from django import template
from django.template.loader import get_template
from django.template.base import FilterExpression

from ..sitetreeapp import get_sitetree, _CONTEXT_FLATTEN

register = template.Library()

@register.tag
def sitetree_tree(parser, token):
    """Parses sitetree tag parameters.

    Two notation types are possible:
        1. Two arguments:
           {% sitetree_tree from "mytree" %}
           Used to render tree for "mytree" site tree.

        2. Four arguments:
           {% sitetree_tree from "mytree" template "sitetree/mytree.html" %}
           Used to render tree for "mytree" site tree using specific
           template "sitetree/mytree.html"

    """
    tokens = token.split_contents()
    use_template = detect_clause(parser, 'template', tokens)
    tokens_num = len(tokens)

    if tokens_num in (3, 5):
        tree_alias = parser.compile_filter(tokens[2])
        return sitetree_treeNode(tree_alias, use_template)
    else:
        raise template.TemplateSyntaxError(
            '%r tag requires two arguments. E.g. {%% sitetree_tree from "mytree" %%}.' % tokens[0])

@register.tag
def sitetree_children(parser, token):
    """Parses sitetree_children tag parameters.

       Six arguments:
           {% sitetree_children of someitem for menu template "sitetree/mychildren.html" %}
           Used to render child items of specific site tree 'someitem'
           using template "sitetree/mychildren.html" for menu navigation.

           Basically template argument should contain path to current template itself.

           Allowed navigation types: 1) menu; 2) sitetree.

    """
    tokens = token.split_contents()
    use_template = detect_clause(parser, 'template', tokens)
    tokens_num = len(tokens)

    clauses_in_places = (
        tokens_num == 5 and tokens[1] == 'of' and tokens[3] == 'for' and tokens[4] in ('menu', 'sitetree')
    )
    if clauses_in_places and use_template is not None:
        tree_item = tokens[2]
        navigation_type = tokens[4]
        return sitetree_childrenNode(tree_item, navigation_type, use_template)
    else:
        raise template.TemplateSyntaxError(
            '%r tag requires six arguments. '
            'E.g. {%% sitetree_children of someitem for menu template "sitetree/mychildren.html" %%}.' % tokens[0])

@register.tag
def sitetree_breadcrumbs(parser, token):
    """Parses sitetree_breadcrumbs tag parameters.

    Two notation types are possible:
        1. Two arguments:
           {% sitetree_breadcrumbs from "mytree" %}
           Used to render breadcrumb path for "mytree" site tree.

        2. Four arguments:
           {% sitetree_breadcrumbs from "mytree" template "sitetree/mycrumb.html" %}
           Used to render breadcrumb path for "mytree" site tree using specific
           template "sitetree/mycrumb.html"

    """
    tokens = token.split_contents()
    use_template = detect_clause(parser, 'template', tokens)
    tokens_num = len(tokens)

    if tokens_num == 3:
        tree_alias = parser.compile_filter(tokens[2])
        return sitetree_breadcrumbsNode(tree_alias, use_template)
    else:
        raise template.TemplateSyntaxError(
            '%r tag requires two arguments. E.g. {%% sitetree_breadcrumbs from "mytree" %%}.' % tokens[0])

@register.tag
def sitetree_menu(parser, token):
    """Parses sitetree_menu tag parameters.

        {% sitetree_menu from "mytree" include "trunk,1,level3" %}
        Used to render trunk, branch with id 1 and branch aliased 'level3'
        elements from "mytree" site tree as a menu.

        These are reserved aliases:
            * 'trunk' - items without parents
            * 'this-children' - items under item resolved as current for the current page
            * 'this-siblings' - items under parent of item resolved as current for
              the current page (current item included)
            * 'this-ancestor-children' - items under grandparent item (closest to root)
              for the item resolved as current for the current page

        {% sitetree_menu from "mytree" include "trunk,1,level3" template "sitetree/mymenu.html" %}

    """
    tokens = token.split_contents()
    use_template = detect_clause(parser, 'template', tokens)
    tokens_num = len(tokens)

    if tokens_num == 5 and tokens[3] == 'include':
        tree_alias = parser.compile_filter(tokens[2])
        tree_branches = parser.compile_filter(tokens[4])
        return sitetree_menuNode(tree_alias, tree_branches, use_template)
    else:
        raise template.TemplateSyntaxError(
            '%r tag requires four arguments. '
            'E.g. {%% sitetree_menu from "mytree" include "trunk,1,level3" %%}.' % tokens[0])

@register.tag
def sitetree_url(parser, token):
    """This tag is much the same as Django built-in 'url' tag.
    The difference is that after 'for' it should get TreeItem object.

    """
    return sitetree_urlNode.for_tag(parser, token, 'for', 'sitetree_url for someitem')

@register.tag
def sitetree_page_title(parser, token):
    """Renders a title for current page, resolved against sitetree item representing current URL."""
    return sitetree_page_titleNode.for_tag(parser, token, 'from', 'sitetree_page_title from "mytree"')

@register.tag
def sitetree_page_description(parser, token):
    """Renders a description for the current page, resolved against sitetree item representing current URL."""
    return sitetree_page_descriptionNode.for_tag(parser, token, 'from', 'sitetree_page_description from "mytree"')

@register.tag
def sitetree_page_hint(parser, token):
    """Renders a hint for the current page, resolved against sitetree item representing current URL."""
    return sitetree_page_hintNode.for_tag(parser, token, 'from', 'sitetree_page_hint from "mytree"')

class sitetree_treeNode(template.Node):
    """Renders tree items from specified site tree."""

    def __init__(self, tree_alias, use_template):
        self.use_template = use_template
        self.tree_alias = tree_alias

    def render(self, context):
        tree_items = get_sitetree().tree(self.tree_alias, context)
        return render(context, tree_items, self.use_template or 'sitetree/tree.html')

class sitetree_childrenNode(template.Node):
    """Renders tree items under specified parent site tree item."""

    def __init__(self, tree_item, navigation_type, use_template):
        self.use_template = use_template
        self.tree_item = tree_item
        self.navigation_type = navigation_type

    def render(self, context):
        return get_sitetree().children(self.tree_item, self.navigation_type, self.use_template.resolve(context), context)

class sitetree_breadcrumbsNode(template.Node):
    """Renders breadcrumb trail items from specified site tree."""

    def __init__(self, tree_alias, use_template):
        self.use_template = use_template
        self.tree_alias = tree_alias

    def render(self, context):
        tree_items = get_sitetree().breadcrumbs(self.tree_alias, context)
        return render(context, tree_items, self.use_template or 'sitetree/breadcrumbs.html')

class sitetree_menuNode(template.Node):
    """Renders specified site tree menu items."""

    def __init__(self, tree_alias, tree_branches, use_template):
        self.use_template = use_template
        self.tree_alias = tree_alias
        self.tree_branches = tree_branches

    def render(self, context):
        tree_items = get_sitetree().menu(self.tree_alias, self.tree_branches, context)
        return render(context, tree_items, self.use_template or 'sitetree/menu.html')

class SimpleNode(template.Node):
    """Simple node with `as` clause support."""

    @classmethod
    def for_tag(cls, parser, token, preposition, error_hint):
        """Node constructor to be used in tags."""
        tokens = token.split_contents()

        if len(tokens) >= 3 and tokens[1] == preposition:
            as_var = cls.get_as_var(tokens)
            tree_alias = parser.compile_filter(tokens[2])
            return cls(tree_alias, as_var)

        raise template.TemplateSyntaxError(
            '%r tag requires at least two arguments. E.g. {%% %s %%}.' % (tokens[0], error_hint))

    @classmethod
    def get_as_var(cls, tokens):
        """Returns context variable from `as` template tag clause if any.

         Modifies tokens inplace.

        :param tokens:
        :rtype: None|str
        """
        as_var = None
        if tokens[-2] == 'as':
            as_var = tokens[-1]
            tokens[-2:] = []
        return as_var

    def __init__(self, item, as_var):
        self.item = item
        self.as_var = as_var

    def get_value(self, context):
        """Should return a computed value to be used in render()."""

    def render(self, context):
        result = self.get_value(context)
        if self.as_var:
            context[self.as_var] = result
            return ''
        return result

class sitetree_urlNode(SimpleNode):
    """Resolves and renders specified url."""

    def get_value(self, context):
        return get_sitetree().url(self.item, context)

class sitetree_page_titleNode(SimpleNode):
    """Renders a page title from the specified site tree."""

    def get_value(self, context):
        return get_sitetree().get_current_page_title(self.item, context)

class sitetree_page_descriptionNode(SimpleNode):
    """Renders a page description from the specified site tree."""

    def get_value(self, context):
        return get_sitetree().get_current_page_attr('description', self.item, context)

class sitetree_page_hintNode(SimpleNode):
    """Renders a page hint from the specified site tree."""

    def get_value(self, context):
        return get_sitetree().get_current_page_attr('hint', self.item, context)

def detect_clause(parser, clause_name, tokens):
    """Helper function detects a certain clause in tag tokens list.
    Returns its value.

    """
    if clause_name in tokens:
        t_index = tokens.index(clause_name)
        clause_value = parser.compile_filter(tokens[t_index + 1])
        del tokens[t_index:t_index + 2]
    else:
        clause_value = None
    return clause_value

def render(context, tree_items, use_template):
    """Render helper is used by template node functions
    to render given template with given tree items in context.

    """
    context.push()
    context['sitetree_items'] = tree_items

    if isinstance(use_template, FilterExpression):
        use_template = use_template.resolve(context)

    content = get_template(use_template).render(context.flatten() if _CONTEXT_FLATTEN else context)
    context.pop()

    return content


from django.conf import settings as django_settings
from django import VERSION as django_version
try:
    from django.urls import get_urlconf, get_resolver
except ImportError:
    from django.core.urlresolvers import get_urlconf, get_resolver
from django.utils.translation import ugettext_lazy as _
from django.utils import six
from django.http import HttpResponseRedirect
from django.contrib import admin
from django.contrib.admin.sites import NotRegistered
from django.contrib import messages
from django.conf.urls import url

DJANGO_POST_19 = django_version >= (1, 9, 0)

if not DJANGO_POST_19:
    from django.conf.urls import patterns as patterns_func

from .settings import MODEL_TREE, MODEL_TREE_ITEM
from .fields import TreeItemChoiceField
from .utils import get_tree_model, get_tree_item_model, get_app_n_model

SMUGGLER_INSTALLED = 'smuggler' in django_settings.INSTALLED_APPS

MODEL_TREE_CLASS = get_tree_model()
MODEL_TREE_ITEM_CLASS = get_tree_item_model()

_TREE_ADMIN = lambda: TreeAdmin
_ITEM_ADMIN = lambda: TreeItemAdmin

def get_model_url_name(model_nfo, page, with_namespace=False):
    """Returns a URL for a given Tree admin page type."""
    prefix = ''
    if with_namespace:
        prefix = 'admin:'
    return ('%s%s_%s' % (prefix, '%s_%s' % model_nfo, page)).lower()

def get_tree_url_name(page, with_namespace=False):
    """Returns a URL for a given Tree admin page type."""
    return get_model_url_name(get_app_n_model('MODEL_TREE'), page, with_namespace)

def get_tree_item_url_name(page, with_namespace=False):
    """Returns a URL for a given Tree Item admin page type."""
    return get_model_url_name(get_app_n_model('MODEL_TREE_ITEM'), page, with_namespace)

_TREE_URLS = {
    'app': get_app_n_model('MODEL_TREE')[0],
    'change': get_tree_url_name('change', True),
    'changelist': get_tree_url_name('changelist', True),
    'treeitem_change': get_tree_item_url_name('change', True)
}

def _reregister_tree_admin():
    """Forces unregistration of tree admin class with following re-registration."""
    try:
        admin.site.unregister(MODEL_TREE_CLASS)
    except NotRegistered:
        pass
    admin.site.register(MODEL_TREE_CLASS, _TREE_ADMIN())

def override_tree_admin(admin_class):
    """Sets a class that should be used instead of TreeAdmin
    to represent trees in the Admin interface.
    Note that the class must inherit from TreeAdmin.

    """
    global _TREE_ADMIN
    _TREE_ADMIN = lambda: admin_class
    _reregister_tree_admin()

def override_item_admin(admin_class):
    """Sets a class that should be used instead of TreeItemAdmin
    to represent tree items in the Admin interface.
    Note that the class must inherit from TreeItemAdmin.

    """
    global _ITEM_ADMIN
    _ITEM_ADMIN = lambda: admin_class
    _reregister_tree_admin()

class TreeItemAdmin(admin.ModelAdmin):

    exclude = ('tree', 'sort_order')
    fieldsets = (
        (_('Basic settings'), {
            'fields': ('parent', 'title', 'url',)
        }),
        (_('Access settings'), {
            'classes': ('collapse',),
            'fields': ('access_loggedin', 'access_guest', 'access_restricted', 'access_permissions', 'access_perm_type')
        }),
        (_('Display settings'), {
            'classes': ('collapse',),
            'fields': ('hidden', 'inmenu', 'inbreadcrumbs', 'insitetree')
        }),
        (_('Additional settings'), {
            'classes': ('collapse',),
            'fields': ('hint', 'description', 'alias', 'urlaspattern')
        }),
    )
    filter_horizontal = ('access_permissions',)
    change_form_template = 'admin/sitetree/treeitem/change_form.html'
    delete_confirmation_template = 'admin/sitetree/treeitem/delete_confirmation.html'

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):

        # The same as for GroupAdmin
        # Avoid a major performance hit resolving permission names which
        # triggers a content_type load:
        if db_field.name == 'access_permissions':
            objects = db_field.remote_field.model.objects if DJANGO_POST_19 else db_field.rel.to.objects
            qs = kwargs.get('queryset', objects)
            kwargs['queryset'] = qs.select_related('content_type')

        return super(TreeItemAdmin, self).formfield_for_manytomany(db_field, request=request, **kwargs)

    def _redirect(self, request, response):
        """Generic redirect for item editor."""

        if '_addanother' in request.POST:
            return HttpResponseRedirect('../item_add/')

        elif '_save' in request.POST:
            return HttpResponseRedirect('../')

        elif '_continue' in request.POST:
            return response

        return HttpResponseRedirect('')

    def response_add(self, request, obj, post_url_continue=None, **kwargs):
        """Redirects to the appropriate items' 'continue' page on item add.

        As we administer tree items within tree itself, we
        should make some changes to redirection process.

        """
        if post_url_continue is None:
            post_url_continue = '../item_%s/' % obj.pk

        return self._redirect(request, super(TreeItemAdmin, self).response_add(request, obj, post_url_continue))

    def response_change(self, request, obj, **kwargs):
        """Redirects to the appropriate items' 'add' page on item change.

        As we administer tree items within tree itself, we
        should make some changes to redirection process.

        """
        return self._redirect(request, super(TreeItemAdmin, self).response_change(request, obj))

    def get_form(self, request, obj=None, **kwargs):
        """Returns modified form for TreeItem model.
        'Parent' field choices are built by sitetree itself.

        """
        if obj is not None and obj.parent is not None:
            self.previous_parent = obj.parent
            previous_parent_id = self.previous_parent.id
        else:
            previous_parent_id = None

        my_choice_field = TreeItemChoiceField(self.tree, initial=previous_parent_id)
        form = super(TreeItemAdmin, self).get_form(request, obj, **kwargs)
        my_choice_field.label = form.base_fields['parent'].label
        my_choice_field.help_text = form.base_fields['parent'].help_text
        my_choice_field.widget = form.base_fields['parent'].widget
        # Replace 'parent' TreeItem field with new appropriate one
        form.base_fields['parent'] = my_choice_field

        # Try to resolve all currently registered url names including those in namespaces.
        if not getattr(self, 'known_url_names', False):
            self.known_url_names = []
            self.known_url_rules = []
            resolver = get_resolver(get_urlconf())
            for ns, (url_prefix, ns_resolver) in resolver.namespace_dict.items():
                if ns != 'admin':
                    self._stack_known_urls(ns_resolver.reverse_dict, ns)
            self._stack_known_urls(resolver.reverse_dict)
            self.known_url_rules = sorted(self.known_url_rules)

        form.known_url_names_hint = _(
            'You are seeing this warning because "URL as Pattern" option is active and pattern entered above '
            'seems to be invalid. Currently registered URL pattern names and parameters: ')
        form.known_url_names = self.known_url_names
        form.known_url_rules = self.known_url_rules
        return form

    def _stack_known_urls(self, reverse_dict, ns=None):
        for url_name, url_rules in reverse_dict.items():
            if isinstance(url_name, six.string_types):
                if ns is not None:
                    url_name = '%s:%s' % (ns, url_name)
                self.known_url_names.append(url_name)
                self.known_url_rules.append('<b>%s</b> %s' % (url_name, ' '.join(url_rules[0][0][1])))

    def get_tree(self, request, tree_id, item_id=None):
        """Fetches Tree for current or given TreeItem."""
        if tree_id is None:
            tree_id = self.get_object(request, item_id).tree_id
        self.tree = MODEL_TREE_CLASS._default_manager.get(pk=tree_id)
        self.tree.verbose_name_plural = self.tree._meta.verbose_name_plural
        self.tree.urls = _TREE_URLS
        return self.tree

    def item_add(self, request, tree_id):
        tree = self.get_tree(request, tree_id)
        return self.add_view(request, extra_context={'tree': tree})

    def item_edit(self, request, item_id, tree_id=None):
        tree = self.get_tree(request, tree_id, item_id)
        return self.change_view(request, item_id, extra_context={'tree': tree})

    def item_delete(self, request, item_id, tree_id=None):
        tree = self.get_tree(request, tree_id, item_id)
        return self.delete_view(request, item_id, extra_context={'tree': tree})

    def item_history(self, request, item_id, tree_id=None):
        tree = self.get_tree(request, tree_id, item_id)
        return self.history_view(request, item_id, extra_context={'tree': tree})

    def item_move(self, request, tree_id, item_id, direction):
        """Moves item up or down by swapping 'sort_order' field values of neighboring items."""
        current_item = MODEL_TREE_ITEM_CLASS._default_manager.get(pk=item_id)
        if direction == 'up':
            sort_order = 'sort_order'
        else:
            sort_order = '-sort_order'

        siblings = MODEL_TREE_ITEM_CLASS._default_manager.filter(
            parent=current_item.parent,
            tree=current_item.tree
        ).order_by(sort_order)

        previous_item = None
        for item in siblings:
            if item != current_item:
                previous_item = item
            else:
                break

        if previous_item is not None:
            current_item_sort_order = current_item.sort_order
            previous_item_sort_order = previous_item.sort_order

            current_item.sort_order = previous_item_sort_order
            previous_item.sort_order = current_item_sort_order

            current_item.save()
            previous_item.save()

        return HttpResponseRedirect('../../')

    def save_model(self, request, obj, form, change):
        """Saves TreeItem model under certain Tree.
        Handles item's parent assignment exception.

        """
        if change:
            # No, you're not allowed to make item parent of itself
            if obj.parent is not None and obj.parent.id == obj.id:
                obj.parent = self.previous_parent
                messages.warning(
                    request, _("Item's parent left unchanged. Item couldn't be parent to itself."), '', True)
        obj.tree = self.tree
        obj.save()

def redirects_handler(*args, **kwargs):
    """Fixes Admin contrib redirects compatibility problems
    introduced in Django 1.4 by url handling changes.

    """
    path = args[0].path
    shift = '../'

    if 'delete' in path:
        # Weird enough 'delete' is not handled by TreeItemAdmin::response_change().
        shift += '../'
    elif 'history' in path:
        if 'item_id' not in kwargs:
            # Encountered request from history page to return to tree layout page.
            shift += '../'

    return HttpResponseRedirect(path + shift)

class TreeAdmin(admin.ModelAdmin):

    list_display = ('alias', 'title')
    list_display_links = ('title', 'alias')
    search_fields = ['title', 'alias']
    ordering = ['title', 'alias']
    actions = None
    change_form_template = 'admin/sitetree/tree/change_form.html'

    def __init__(self, *args, **kwargs):

        if SMUGGLER_INSTALLED:
            self.change_list_template = 'admin/sitetree/tree/change_list_.html'

        super(TreeAdmin, self).__init__(*args, **kwargs)
        self.tree_admin = _ITEM_ADMIN()(MODEL_TREE_ITEM_CLASS, admin.site)

    def render_change_form(self, request, context, **kwargs):
        context['icon_ext'] = '.svg' if DJANGO_POST_19 else '.gif'
        context['django19'] = DJANGO_POST_19
        return super(TreeAdmin, self).render_change_form(request, context, **kwargs)

    def get_urls(self):
        """Manages not only TreeAdmin URLs but also TreeItemAdmin URLs."""
        urls = super(TreeAdmin, self).get_urls()

        prefix_change = 'change/' if DJANGO_POST_19 else ''

        sitetree_urls = [
            url(r'^change/$', redirects_handler, name=get_tree_item_url_name('changelist')),

            url(r'^((?P<tree_id>\d+)/)?%sitem_add/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_add), name=get_tree_item_url_name('add')),

            url(r'^(?P<tree_id>\d+)/%sitem_(?P<item_id>\d+)/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_edit), name=get_tree_item_url_name('change')),

            url(r'^%sitem_(?P<item_id>\d+)/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_edit), name=get_tree_item_url_name('change')),

            url(r'^((?P<tree_id>\d+)/)?%sitem_(?P<item_id>\d+)/delete/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_delete), name=get_tree_item_url_name('delete')),

            url(r'^((?P<tree_id>\d+)/)?%sitem_(?P<item_id>\d+)/history/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_history), name=get_tree_item_url_name('history')),

            url(r'^(?P<tree_id>\d+)/%sitem_(?P<item_id>\d+)/move_(?P<direction>(up|down))/$' % prefix_change,
                self.admin_site.admin_view(self.tree_admin.item_move), name=get_tree_item_url_name('move')),
        ]

        if not DJANGO_POST_19:
            sitetree_urls = patterns_func('', *sitetree_urls)

        if SMUGGLER_INSTALLED:
            sitetree_urls += (url(r'^dump_all/$', self.admin_site.admin_view(self.dump_view), name='sitetree_dump'),)

        return sitetree_urls + urls

    @classmethod
    def dump_view(cls, request):
        """Dumps sitetrees with items using django-smuggler.

        :param request:
        :return:
        """
        from smuggler.views import dump_to_response
        return dump_to_response(request, [MODEL_TREE, MODEL_TREE_ITEM], filename_prefix='sitetrees')

_reregister_tree_admin()


from importlib import import_module

from django.apps import apps
from django.contrib.auth.models import Permission
from django.core.exceptions import ImproperlyConfigured
from django.utils import six
from django.utils.module_loading import module_has_submodule

apps_get_model = apps.get_model

from sitetree import settings

def generate_id_for(obj):
    """Generates and returns a unique identifier for the given object."""
    return id(obj)

def tree(alias, title='', items=None, **kwargs):
    """Dynamically creates and returns a sitetree.

    :param str|unicode alias:
    :param str|unicode title:
    :param iterable items: dynamic sitetree items objects created by `item` function.
    :param kwargs: Additional arguments to pass to tree item initializer.

    :rtype: TreeBase
    """
    tree_obj = get_tree_model()(alias=alias, title=title, **kwargs)
    tree_obj.id = generate_id_for(tree_obj)
    tree_obj.is_dynamic = True

    if items is not None:
        tree_obj.dynamic_items = []
        def traverse(items):
            for item in items:
                item.tree = tree_obj
                tree_obj.dynamic_items.append(item)
                if hasattr(item, 'dynamic_children'):
                    traverse(item.dynamic_children)

        traverse(items)
    return tree_obj

def item(
    title, url, children=None, url_as_pattern=True, hint='', alias='', description='',
    in_menu=True, in_breadcrumbs=True, in_sitetree=True,
    access_loggedin=False, access_guest=False,
    access_by_perms=None, perms_mode_all=True, **kwargs):
    """Dynamically creates and returns a sitetree item object.

    :param str|unicode title:

    :param str|unicode url:

    :param list, set children: a list of children for tree item. Children should also be created by `item` function.

    :param bool url_as_pattern: consider URL as a name of a named URL

    :param str|unicode hint: hints are usually shown to users

    :param str|unicode alias: item name to address it from templates

    :param str|unicode description: additional information on item (usually is not shown to users)

    :param bool in_menu: show this item in menus

    :param bool in_breadcrumbs: show this item in breadcrumbs

    :param bool in_sitetree: show this item in sitetrees

    :param bool access_loggedin: show item to logged in users only

    :param bool access_guest: show item to guest users only

    :param list|str||unicode|int, Permission access_by_perms: restrict access to users with these permissions.

        This can be set to one or a list of permission names, IDs or Permission instances.

        Permission names are more portable and should be in a form `<app_label>.<perm_codename>`, e.g.:
            my_app.allow_save

    :param bool perms_mode_all: permissions set interpretation rule:
                True - user should have all the permissions;
                False - user should have any of chosen permissions.

    :rtype: TreeItemBase

    """
    item_obj = get_tree_item_model()(
        title=title, url=url, urlaspattern=url_as_pattern,
        hint=hint, alias=alias, description=description, inmenu=in_menu,
        insitetree=in_sitetree, inbreadcrumbs=in_breadcrumbs,
        access_loggedin=access_loggedin, access_guest=access_guest,
        **kwargs)

    item_obj.id = generate_id_for(item_obj)
    item_obj.is_dynamic = True
    item_obj.dynamic_children = []

    cleaned_permissions = []
    if access_by_perms:
        # Make permissions a list if currently a single object
        if not isinstance(access_by_perms, list):
            access_by_perms = [access_by_perms]

        for perm in access_by_perms:
            if isinstance(perm, six.string_types):
                # Get permission object from string
                try:
                    app, codename = perm.split('.')
                except ValueError:
                    raise ValueError(
                        'Wrong permission string format: supplied - `%s`; '
                        'expected - `<app_name>.<permission_name>`.' % perm)

                try:
                    perm = Permission.objects.get(codename=codename, content_type__app_label=app)
                except Permission.DoesNotExist:
                    raise ValueError('Permission `%s.%s` does not exist.' % (app, codename))

            elif not isinstance(perm, (int, Permission)):
                raise ValueError('Permissions must be given as strings, ints, or `Permission` instances.')

            cleaned_permissions.append(perm)

    item_obj.permissions = cleaned_permissions or []
    item_obj.access_perm_type = item_obj.PERM_TYPE_ALL if perms_mode_all else item_obj.PERM_TYPE_ANY

    if item_obj.permissions:
        item_obj.access_restricted = True

    if children is not None:
        for child in children:
            child.parent = item_obj
            item_obj.dynamic_children.append(child)

    return item_obj

def import_app_sitetree_module(app):
    """Imports sitetree module from a given app.

    :param str|unicode app: Application name
    :return: module|None
    """
    module_name = settings.APP_MODULE_NAME
    module = import_module(app)
    try:
        sub_module = import_module('%s.%s' % (app, module_name))
        return sub_module
    except ImportError:
        if module_has_submodule(module, module_name):
            raise
        return None

def import_project_sitetree_modules():
    """Imports sitetrees modules from packages (apps).
    Returns a list of submodules.

    :rtype: list
    """
    from django.conf import settings as django_settings
    submodules = []
    for app in django_settings.INSTALLED_APPS:
        module = import_app_sitetree_module(app)
        if module is not None:
            submodules.append(module)
    return submodules

def get_app_n_model(settings_entry_name):
    """Returns tuple with application and tree[item] model class names.

    :param str|unicode settings_entry_name:
    :rtype: tuple
    """
    try:
        app_name, model_name = getattr(settings, settings_entry_name).split('.')
    except ValueError:
        raise ImproperlyConfigured(
            '`SITETREE_%s` must have the following format: `app_name.model_name`.' % settings_entry_name)
    return app_name, model_name

def get_model_class(settings_entry_name):
    """Returns a certain sitetree model as defined in the project settings.

    :param str|unicode settings_entry_name:
    :rtype: TreeItemBase|TreeBase
    """
    app_name, model_name = get_app_n_model(settings_entry_name)

    try:
        model = apps_get_model(app_name, model_name)
    except (LookupError, ValueError):
        model = None

    if model is None:
        raise ImproperlyConfigured(
            '`SITETREE_%s` refers to model `%s` that has not been installed.' % (settings_entry_name, model_name))

    return model

def get_tree_model():
    """Returns the Tree model, set for the project.

    :rtype: TreeBase
    """
    return get_model_class('MODEL_TREE')

def get_tree_item_model():
    """Returns the TreeItem model, set for the project.

    :rtype: TreeItemBase
    """
    return get_model_class('MODEL_TREE_ITEM')


import asyncio
from itertools import chain
from time import time
from typing import List, Optional, Tuple
from urllib.parse import unquote

import h11

from .run import H2CProtocolRequired, H2ProtocolAssumed, WebsocketProtocolRequired
from .utils import ASGIHTTPState, build_and_validate_headers, UnexpectedMessage
from ..config import Config
from ..typing import ASGIFramework, H11SendableEvent
from ..utils import invoke_asgi, suppress_body

class H11Mixin:
    # This handles a h11 request in the ASGI system, all I/O
    # (including when to close) should be handled by the actual worker
    # rather than this class.

    app: ASGIFramework
    client: Tuple[str, int]
    config: Config
    response: Optional[dict]
    server: Tuple[str, int]
    state: ASGIHTTPState

    @property
    def scheme(self) -> str:
        pass

    def response_headers(self) -> List[Tuple[bytes, bytes]]:
        pass

    async def asend(self, event: H11SendableEvent) -> None:
        pass

    async def asgi_put(self, message: dict) -> None:
        """Called by the ASGI server to put a message to the ASGI instance.

        See asgi_receive as the get to this put.
        """
        pass

    async def asgi_receive(self) -> dict:
        """Called by the ASGI instance to receive a message."""
        pass

    def error_response(self, status_code: int) -> h11.Response:
        return h11.Response(
            status_code=status_code,
            headers=chain(
                [(b"content-length", b"0"), (b"connection", b"close")], self.response_headers()
            ),
        )

    def raise_if_upgrade(self, event: h11.Request, trailing_data: bytes) -> None:
        upgrade_value = ""
        connection_value = ""
        has_body = False
        for name, value in event.headers:
            sanitised_name = name.decode().strip().lower()
            if sanitised_name == "upgrade":
                upgrade_value = value.decode().strip()
            elif sanitised_name == "connection":
                connection_value = value.decode().strip()
            elif sanitised_name in {"content-length", "transfer-encoding"}:
                has_body = True

        connection_tokens = connection_value.lower().split(",")
        if (
            any(token.strip() == "upgrade" for token in connection_tokens)
            and upgrade_value.lower() == "websocket"
            and event.method.decode().upper() == "GET"
        ):
            raise WebsocketProtocolRequired(event)
        # h2c Upgrade requests with a body are a pain as the body must
        # be fully recieved in HTTP/1.1 before the upgrade response
        # and HTTP/2 takes over, so Hypercorn ignores the upgrade and
        # responds in HTTP/1.1. Use a preflight OPTIONS request to
        # initiate the upgrade if really required (or just use h2).
        elif upgrade_value.lower() == "h2c" and not has_body:
            raise H2CProtocolRequired(event)
        elif event.method == b"PRI" and event.target == b"*" and event.http_version == b"2.0":
            raise H2ProtocolAssumed(b"PRI * HTTP/2.0\r\n\r\n" + trailing_data)

    async def handle_request(self, request: h11.Request) -> None:
        path, _, query_string = request.target.partition(b"?")
        self.scope = {
            "type": "http",
            "http_version": request.http_version.decode(),
            "asgi": {"spec_version": "2.1"},
            "method": request.method.decode().upper(),
            "scheme": self.scheme,
            "path": unquote(path.decode("ascii")),
            "query_string": query_string,
            "root_path": self.config.root_path,
            "headers": request.headers,
            "client": self.client,
            "server": self.server,
        }
        await self.handle_asgi_app()

    async def handle_asgi_app(self) -> None:
        start_time = time()
        try:
            await invoke_asgi(self.app, self.scope, self.asgi_receive, self.asgi_send)
        except asyncio.CancelledError:
            pass
        except Exception:
            if self.config.error_logger is not None:
                self.config.error_logger.exception("Error in ASGI Framework")

        # If the application hasn't sent a response, it has errored -
        # send a 500 for it.
        if self.state == ASGIHTTPState.REQUEST:
            await self.asend(self.error_response(500))
            await self.asend(h11.EndOfMessage())
            self.response = {"status": 500, "headers": []}

        self.config.access_logger.access(self.scope, self.response, time() - start_time)

    async def asgi_send(self, message: dict) -> None:
        """Called by the ASGI instance to send a message."""
        if message["type"] == "http.response.start" and self.state == ASGIHTTPState.REQUEST:
            self.response = message
        elif message["type"] == "http.response.body" and self.state in {
            ASGIHTTPState.REQUEST,
            ASGIHTTPState.RESPONSE,
        }:
            if self.state == ASGIHTTPState.REQUEST:
                headers = build_and_validate_headers(self.response["headers"])
                headers.extend(self.response_headers())
                await self.asend(
                    h11.Response(status_code=int(self.response["status"]), headers=headers)
                )
                self.state = ASGIHTTPState.RESPONSE

            if (
                not suppress_body(self.scope["method"], int(self.response["status"]))
                and message.get("body", b"") != b""
            ):
                await self.asend(h11.Data(data=bytes(message["body"])))

            if not message.get("more_body", False):
                if self.state != ASGIHTTPState.CLOSED:
                    await self.asend(h11.EndOfMessage())
                    await self.asgi_put({"type": "http.disconnect"})
                    self.state = ASGIHTTPState.CLOSED
        else:
            raise UnexpectedMessage(self.state, message["type"])


import asyncio
from time import time
from typing import List, Optional, Tuple, Union
from urllib.parse import unquote

from wsproto.events import (
    AcceptConnection,
    BytesMessage,
    CloseConnection,
    Event,
    RejectConnection,
    RejectData,
    Request,
    TextMessage,
)
from wsproto.extensions import PerMessageDeflate
from wsproto.frame_protocol import CloseReason

from .utils import (
    ASGIWebsocketState,
    build_and_validate_headers,
    raise_if_subprotocol_present,
    UnexpectedMessage,
)
from ..config import Config
from ..typing import ASGIFramework
from ..utils import invoke_asgi, suppress_body

class WebsocketMixin:
    app: ASGIFramework
    client: Tuple[str, int]
    config: Config
    response: Optional[dict]
    server: Tuple[str, int]
    start_time: float
    state: ASGIWebsocketState

    @property
    def scheme(self) -> str:
        pass

    def response_headers(self) -> List[Tuple[bytes, bytes]]:
        pass

    async def asend(self, event: Event) -> None:
        pass

    async def asgi_put(self, message: dict) -> None:
        """Called by the ASGI server to put a message to the ASGI instance.

        See asgi_receive as the get to this put.
        """
        pass

    async def asgi_receive(self) -> dict:
        """Called by the ASGI instance to receive a message."""
        pass

    async def handle_websocket(self, event: Request) -> None:
        path, _, query_string = event.target.partition("?")
        headers = [(b"host", event.host.encode())]
        headers.extend(event.extra_headers)
        self.scope = {
            "type": "websocket",
            "asgi": {"spec_version": "2.1"},
            "http_version": "1.1",
            "scheme": self.scheme,
            "path": unquote(path),
            "query_string": query_string.encode("ascii"),
            "root_path": self.config.root_path,
            "headers": headers,
            "client": self.client,
            "server": self.server,
            "subprotocols": event.subprotocols,
            "extensions": {"websocket.http.response": {}},
        }
        await self.handle_asgi_app(event)

    async def send_http_error(self, status: int) -> None:
        await self.asend(RejectConnection(status_code=status, headers=self.response_headers()))
        self.config.access_logger.access(
            self.scope, {"status": status, "headers": []}, time() - self.start_time
        )

    async def handle_asgi_app(self, event: Request) -> None:
        self.start_time = time()
        await self.asgi_put({"type": "websocket.connect"})
        try:
            await invoke_asgi(self.app, self.scope, self.asgi_receive, self.asgi_send)
        except asyncio.CancelledError:
            pass
        except Exception:
            if self.config.error_logger is not None:
                self.config.error_logger.exception("Error in ASGI Framework")

            if self.state == ASGIWebsocketState.CONNECTED:
                await self.asend(CloseConnection(code=CloseReason.ABNORMAL_CLOSURE))
                self.state = ASGIWebsocketState.CLOSED

        # If the application hasn't accepted the connection (or sent a
        # response) send a 500 for it. Otherwise if the connection
        # hasn't been closed then close it.
        if self.state == ASGIWebsocketState.HANDSHAKE:
            await self.send_http_error(500)
            self.state = ASGIWebsocketState.HTTPCLOSED

    async def asgi_send(self, message: dict) -> None:
        """Called by the ASGI instance to send a message."""
        if message["type"] == "websocket.accept" and self.state == ASGIWebsocketState.HANDSHAKE:
            headers = build_and_validate_headers(message.get("headers", []))
            raise_if_subprotocol_present(headers)
            headers.extend(self.response_headers())
            await self.asend(
                AcceptConnection(
                    extensions=[PerMessageDeflate()],
                    extra_headers=headers,
                    subprotocol=message.get("subprotocol"),
                )
            )
            self.state = ASGIWebsocketState.CONNECTED
            self.config.access_logger.access(
                self.scope, {"status": 101, "headers": []}, time() - self.start_time
            )
        elif (
            message["type"] == "websocket.http.response.start"
            and self.state == ASGIWebsocketState.HANDSHAKE
        ):
            self.response = message
            self.config.access_logger.access(self.scope, self.response, time() - self.start_time)
        elif message["type"] == "websocket.http.response.body" and self.state in {
            ASGIWebsocketState.HANDSHAKE,
            ASGIWebsocketState.RESPONSE,
        }:
            await self._asgi_send_rejection(message)
        elif message["type"] == "websocket.send" and self.state == ASGIWebsocketState.CONNECTED:
            data: Union[bytes, str]
            if message.get("bytes") is not None:
                await self.asend(BytesMessage(data=bytes(message["bytes"])))
            elif not isinstance(message["text"], str):
                raise TypeError(f"{message['text']} should be a str")
            else:
                await self.asend(TextMessage(data=message["text"]))
        elif message["type"] == "websocket.close" and self.state == ASGIWebsocketState.HANDSHAKE:
            await self.send_http_error(403)
            self.state = ASGIWebsocketState.HTTPCLOSED
        elif message["type"] == "websocket.close":
            await self.asend(CloseConnection(code=int(message["code"])))
            self.state = ASGIWebsocketState.CLOSED
        else:
            raise UnexpectedMessage(self.state, message["type"])

    async def _asgi_send_rejection(self, message: dict) -> None:
        body_suppressed = suppress_body("GET", self.response["status"])
        if self.state == ASGIWebsocketState.HANDSHAKE:
            headers = build_and_validate_headers(self.response["headers"])
            headers.extend(self.response_headers())
            await self.asend(
                RejectConnection(
                    status_code=int(self.response["status"]),
                    headers=headers,
                    has_body=not body_suppressed,
                )
            )
            self.state = ASGIWebsocketState.RESPONSE
        if not body_suppressed:
            await self.asend(
                RejectData(
                    data=bytes(message.get("body", b"")),
                    body_finished=not message.get("more_body", False),
                )
            )
        if not message.get("more_body", False):
            await self.asgi_put({"type": "websocket.disconnect"})
            self.state = ASGIWebsocketState.HTTPCLOSED


import warnings

from .run import worker_serve
from ..config import Config
from ..typing import ASGIFramework

async def serve(app: ASGIFramework, config: Config) -> None:
    """Serve an ASGI framework app given the config.

    This allows for a programmatic way to serve an ASGI framework, it
    can be used via,

    .. code-block:: python

        asyncio.run(serve(app, config))

    It is assumed that the event-loop is configured before calling
    this function, therefore configuration values that relate to loop
    setup or process setup are ignored.

    """
    if config.debug:
        warnings.warn("The config `debug` has no affect when using serve", Warning)
    if config.workers != 1:
        warnings.warn("The config `workers` has no affect when using serve", Warning)
    if config.worker_class != "asyncio":
        warnings.warn("The config `worker_class` has no affect when using serve", Warning)

    await worker_serve(app, config)


import warnings

import trio

from .run import worker_serve
from ..config import Config
from ..typing import ASGIFramework

async def serve(
    app: ASGIFramework,
    config: Config,
    *,
    task_status: trio._core._run._TaskStatus = trio.TASK_STATUS_IGNORED,
) -> None:
    """Serve an ASGI framework app given the config.

    This allows for a programmatic way to serve an ASGI framework, it
    can be used via,

    .. code-block:: python

        trio.run(partial(serve, app, config))

    It is assumed that the event-loop is configured before calling
    this function, therefore configuration values that relate to loop
    setup or process setup are ignored.

    """
    if config.debug:
        warnings.warn("The config `debug` has no affect when using serve", Warning)
    if config.workers != 1:
        warnings.warn("The config `workers` has no affect when using serve", Warning)
    if config.worker_class != "asyncio":
        warnings.warn("The config `worker_class` has no affect when using serve", Warning)

    await worker_serve(app, config, task_status=task_status)


import importlib
import importlib.util
import logging
import os
import socket
import ssl
import stat
import sys
import types
import warnings
from dataclasses import dataclass
from ssl import SSLContext, VerifyFlags, VerifyMode  # type: ignore
from typing import Any, AnyStr, Dict, List, Mapping, Optional, Type, Union

import toml

from .logging import AccessLogger

BYTES = 1
OCTETS = 1
SECONDS = 1.0

FilePath = Union[AnyStr, os.PathLike]

@dataclass
class Sockets:
    secure_sockets: List[socket.socket]
    insecure_sockets: List[socket.socket]

class Config:

    _access_logger: Optional[AccessLogger] = None
    _error_log_target: Optional[str] = None
    _bind = ["127.0.0.1:8000"]
    _insecure_bind: List[str] = []

    access_log_format = "%(h)s %(S)s %(r)s %(s)s %(b)s %(D)s"
    access_log_target: Optional[str] = None
    access_logger_class = AccessLogger
    alpn_protocols = ["h2", "http/1.1"]
    application_path: str
    backlog = 100
    ca_certs: Optional[str] = None
    certfile: Optional[str] = None
    ciphers: str = "ECDHE+AESGCM"
    debug = False
    error_logger: Optional[logging.Logger] = None
    h11_max_incomplete_size = 16 * 1024 * BYTES
    h2_max_concurrent_streams = 100
    h2_max_header_list_size = 2 ** 16
    h2_max_inbound_frame_size = 2 ** 14 * OCTETS
    keep_alive_timeout = 5 * SECONDS
    keyfile: Optional[str] = None
    pid_path: Optional[str] = None
    root_path = ""
    shutdown_timeout = 60 * SECONDS
    ssl_handshake_timeout = 60 * SECONDS
    startup_timeout = 60 * SECONDS
    use_reloader = False
    verify_flags: Optional[VerifyFlags] = None
    verify_mode: Optional[VerifyMode] = None
    websocket_max_message_size = 16 * 1024 * 1024 * BYTES
    worker_class = "asyncio"
    workers = 1

    def set_cert_reqs(self, value: int) -> None:
        warnings.warn("Please use verify_mode instead", Warning)
        self.verify_mode = VerifyMode(value)

    cert_reqs = property(None, set_cert_reqs)

    @property
    def access_logger(self) -> AccessLogger:
        if self._access_logger is None:
            self._access_logger = self.access_logger_class(
                self.access_log_format, self.access_log_target
            )
        return self._access_logger

    @access_logger.setter
    def access_logger(self, value: logging.Logger) -> None:
        self._access_logger = self.access_logger_class(self.access_log_format, value)

    @property
    def error_log_target(self) -> Optional[str]:
        return self._error_log_target

    @error_log_target.setter
    def error_log_target(self, value: Optional[str]) -> None:
        self._error_log_target = value
        if self.error_log_target is not None:
            self.error_logger = logging.getLogger("hypercorn.error")
            if self.error_log_target == "-":
                self.error_logger.addHandler(logging.StreamHandler(sys.stderr))
            else:
                self.error_logger.addHandler(logging.FileHandler(self.error_log_target))
            self.error_logger.setLevel(logging.INFO)

    @property
    def bind(self) -> List[str]:
        return self._bind

    @bind.setter
    def bind(self, value: Union[List[str], str]) -> None:
        if isinstance(value, str):
            self._bind = [value]
        else:
            self._bind = value

    @property
    def insecure_bind(self) -> List[str]:
        return self._insecure_bind

    @insecure_bind.setter
    def insecure_bind(self, value: Union[List[str], str]) -> None:
        if isinstance(value, str):
            self._insecure_bind = [value]
        else:
            self._insecure_bind = value

    def create_ssl_context(self) -> Optional[SSLContext]:
        if not self.ssl_enabled:
            return None

        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.set_ciphers(self.ciphers)
        cipher_opts = 0
        for attr in ["OP_NO_SSLv2", "OP_NO_SSLv3", "OP_NO_TLSv1", "OP_NO_TLSv1_1"]:
            if hasattr(ssl, attr):  # To be future proof
                cipher_opts |= getattr(ssl, attr)
        context.options |= cipher_opts  # RFC 7540 Section 9.2: MUST be TLS >=1.2
        context.options |= ssl.OP_NO_COMPRESSION  # RFC 7540 Section 9.2.1: MUST disable compression
        context.set_alpn_protocols(self.alpn_protocols)
        try:
            context.set_npn_protocols(self.alpn_protocols)
        except NotImplementedError:
            pass  # NPN is not necessarily available

        context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
        if self.ca_certs is not None:
            context.load_verify_locations(self.ca_certs)
        if self.verify_mode is not None:
            context.verify_mode = self.verify_mode
        if self.verify_flags is not None:
            context.verify_flags = self.verify_flags

        return context

    @property
    def ssl_enabled(self) -> bool:
        return self.certfile is not None and self.keyfile is not None

    def create_sockets(self) -> Sockets:
        if self.ssl_enabled:
            secure_sockets = self._create_sockets(self.bind)
            insecure_sockets = self._create_sockets(self.insecure_bind)
        else:
            secure_sockets = []
            insecure_sockets = self._create_sockets(self.bind)
        return Sockets(secure_sockets, insecure_sockets)

    def _create_sockets(self, binds: List[str]) -> List[socket.socket]:
        sockets: List[socket.socket] = []
        for bind in binds:
            binding: Any = None
            if bind.startswith("unix:"):
                sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                binding = bind[5:]
                try:
                    if stat.S_ISSOCK(os.stat(binding).st_mode):
                        os.remove(binding)
                except FileNotFoundError:
                    pass
            elif bind.startswith("fd://"):
                sock = socket.fromfd(int(bind[5:]), socket.AF_UNIX, socket.SOCK_STREAM)
            else:
                try:
                    value = bind.rsplit(":", 1)
                    host, port = value[0], int(value[1])
                except (ValueError, IndexError):
                    host, port = bind, 8000
                sock = socket.socket(
                    socket.AF_INET6 if ":" in host else socket.AF_INET, socket.SOCK_STREAM
                )
                if self.workers > 1:
                    try:
                        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
                    except AttributeError:
                        pass
                binding = (host, port)

            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            if binding is not None:
                sock.bind(binding)
            sock.setblocking(False)
            try:
                sock.set_inheritable(True)  # type: ignore
            except AttributeError:
                pass
            sockets.append(sock)
        return sockets

    @classmethod
    def from_mapping(
        cls: Type["Config"], mapping: Optional[Mapping[str, Any]] = None, **kwargs: Any
    ) -> "Config":
        """Create a configuration from a mapping.

        This allows either a mapping to be directly passed or as
        keyword arguments, for example,

        .. code-block:: python

            config = {'keep_alive_timeout': 10}
            Config.from_mapping(config)
            Config.form_mapping(keep_alive_timeout=10)

        Arguments:
            mapping: Optionally a mapping object.
            kwargs: Optionally a collection of keyword arguments to
                form a mapping.
        """
        mappings: Dict[str, Any] = {}
        if mapping is not None:
            mappings.update(mapping)
        mappings.update(kwargs)
        config = cls()
        for key, value in mappings.items():
            try:
                setattr(config, key, value)
            except AttributeError:
                pass

        return config

    @classmethod
    def from_pyfile(cls: Type["Config"], filename: FilePath) -> "Config":
        """Create a configuration from a Python file.

        .. code-block:: python

            Config.from_pyfile('hypercorn_config.py')

        Arguments:
            filename: The filename which gives the path to the file.
        """
        file_path = os.fspath(filename)
        spec = importlib.util.spec_from_file_location("module.name", file_path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)  # type: ignore
        return cls.from_object(module)

    @classmethod
    def from_toml(cls: Type["Config"], filename: FilePath) -> "Config":
        """Load the configuration values from a TOML formatted file.

        This allows configuration to be loaded as so

        .. code-block:: python

            Config.from_toml('config.toml')

        Arguments:
            filename: The filename which gives the path to the file.
        """
        file_path = os.fspath(filename)
        with open(file_path) as file_:
            data = toml.load(file_)
        return cls.from_mapping(data)

    @classmethod
    def from_object(cls: Type["Config"], instance: Union[object, str]) -> "Config":
        """Create a configuration from a Python object.

        This can be used to reference modules or objects within
        modules for example,

        .. code-block:: python

            Config.from_object('module')
            Config.from_object('module.instance')
            from module import instance
            Config.from_object(instance)

        are valid.

        Arguments:
            instance: Either a str referencing a python object or the
                object itself.

        """
        if isinstance(instance, str):
            try:
                path, config = instance.rsplit(".", 1)
            except ValueError:
                path = instance
                instance = importlib.import_module(instance)
            else:
                module = importlib.import_module(path)
                instance = getattr(module, config)

        mapping = {
            key: getattr(instance, key)
            for key in dir(instance)
            if not isinstance(getattr(instance, key), types.ModuleType)
        }
        return cls.from_mapping(mapping)


# -*- coding: utf-8 -*-
import os
import time

from py_zipkin import Kind
from py_zipkin.encoding._encoders import get_encoder
from py_zipkin.encoding._helpers import copy_endpoint_with_new_service_name
from py_zipkin.encoding._helpers import Span
from py_zipkin.exception import ZipkinError
from py_zipkin.transport import BaseTransportHandler

LOGGING_END_KEY = 'py_zipkin.logging_end'

class ZipkinLoggingContext(object):
    """A logging context specific to a Zipkin trace. If the trace is sampled,
    the logging context sends serialized Zipkin spans to a transport_handler.
    The logging context sends root "server" or "client" span, as well as all
    local child spans collected within this context.

    This class should only be used by the main `zipkin_span` entrypoint.
    """

    def __init__(
        self,
        zipkin_attrs,
        endpoint,
        span_name,
        transport_handler,
        report_root_timestamp,
        get_tracer,
        service_name,
        binary_annotations=None,
        add_logging_annotation=False,
        client_context=False,
        max_span_batch_size=None,
        firehose_handler=None,
        encoding=None,
    ):
        self.zipkin_attrs = zipkin_attrs
        self.endpoint = endpoint
        self.span_name = span_name
        self.transport_handler = transport_handler
        self.response_status_code = 0
        self._get_tracer = get_tracer
        self.service_name = service_name
        self.report_root_timestamp = report_root_timestamp
        self.tags = binary_annotations or {}
        self.add_logging_annotation = add_logging_annotation
        self.client_context = client_context
        self.max_span_batch_size = max_span_batch_size
        self.firehose_handler = firehose_handler

        self.remote_endpoint = None
        self.encoder = get_encoder(encoding)

    def start(self):
        """Actions to be taken before request is handled."""

        # Record the start timestamp.
        self.start_timestamp = time.time()
        return self

    def stop(self):
        """Actions to be taken post request handling.
        """

        self.emit_spans()

    def emit_spans(self):
        """Main function to log all the annotations stored during the entire
        request. This is done if the request is sampled and the response was
        a success. It also logs the service (`ss` and `sr`) or the client
        ('cs' and 'cr') annotations.
        """

        # FIXME: Should have a single aggregate handler
        if self.firehose_handler:
            # FIXME: We need to allow different batching settings per handler
            self._emit_spans_with_span_sender(
                ZipkinBatchSender(self.firehose_handler,
                                  self.max_span_batch_size,
                                  self.encoder)
            )

        if not self.zipkin_attrs.is_sampled:
            self._get_tracer().clear()
            return

        span_sender = ZipkinBatchSender(self.transport_handler,
                                        self.max_span_batch_size,
                                        self.encoder)

        self._emit_spans_with_span_sender(span_sender)
        self._get_tracer().clear()

    def _emit_spans_with_span_sender(self, span_sender):
        with span_sender:
            end_timestamp = time.time()

            # Collect, annotate, and log client spans from the logging handler
            for span in self._get_tracer()._span_storage:
                span.local_endpoint = copy_endpoint_with_new_service_name(
                    self.endpoint,
                    span.local_endpoint.service_name,
                )

                span_sender.add_span(span)

            annotations = {}

            if self.add_logging_annotation:
                annotations[LOGGING_END_KEY] = time.time()

            span_sender.add_span(Span(
                trace_id=self.zipkin_attrs.trace_id,
                name=self.span_name,
                parent_id=self.zipkin_attrs.parent_span_id,
                span_id=self.zipkin_attrs.span_id,
                kind=Kind.CLIENT if self.client_context else Kind.SERVER,
                timestamp=self.start_timestamp,
                duration=end_timestamp - self.start_timestamp,
                local_endpoint=self.endpoint,
                remote_endpoint=self.remote_endpoint,
                shared=not self.report_root_timestamp,
                annotations=annotations,
                tags=self.tags,
            ))

class ZipkinBatchSender(object):

    MAX_PORTION_SIZE = 100

    def __init__(self, transport_handler, max_portion_size, encoder):
        self.transport_handler = transport_handler
        self.max_portion_size = max_portion_size or self.MAX_PORTION_SIZE
        self.encoder = encoder

        if isinstance(self.transport_handler, BaseTransportHandler):
            self.max_payload_bytes = self.transport_handler.get_max_payload_bytes()
        else:
            self.max_payload_bytes = None

    def __enter__(self):
        self._reset_queue()
        return self

    def __exit__(self, _exc_type, _exc_value, _exc_traceback):
        if any((_exc_type, _exc_value, _exc_traceback)):
            filename = os.path.split(_exc_traceback.tb_frame.f_code.co_filename)[1]
            error = '({0}:{1}) {2}: {3}'.format(
                filename,
                _exc_traceback.tb_lineno,
                _exc_type.__name__,
                _exc_value,
            )
            raise ZipkinError(error)
        else:
            self.flush()

    def _reset_queue(self):
        self.queue = []
        self.current_size = 0

    def add_span(self, internal_span):
        encoded_span = self.encoder.encode_span(internal_span)

        # If we've already reached the max batch size or the new span doesn't
        # fit in max_payload_bytes, send what we've collected until now and
        # start a new batch.
        is_over_size_limit = (
            self.max_payload_bytes is not None and
            not self.encoder.fits(
                current_count=len(self.queue),
                current_size=self.current_size,
                max_size=self.max_payload_bytes,
                new_span=encoded_span,
            )
        )
        is_over_portion_limit = len(self.queue) >= self.max_portion_size
        if is_over_size_limit or is_over_portion_limit:
            self.flush()

        self.queue.append(encoded_span)
        self.current_size += len(encoded_span)

    def flush(self):
        if self.transport_handler and len(self.queue) > 0:

            message = self.encoder.encode_queue(self.queue)
            self.transport_handler(message)
        self._reset_queue()


# -*- coding: utf-8 -*-
import functools
import logging
import random
import time
from collections import namedtuple

from py_zipkin import Encoding
from py_zipkin import Kind
from py_zipkin import storage
from py_zipkin.encoding._helpers import create_endpoint
from py_zipkin.encoding._helpers import Span
from py_zipkin.exception import ZipkinError
from py_zipkin.logging_helper import ZipkinLoggingContext
from py_zipkin.storage import get_default_tracer
from py_zipkin.util import generate_random_128bit_string
from py_zipkin.util import generate_random_64bit_string

log = logging.getLogger(__name__)

"""
Holds the basic attributes needed to log a zipkin trace

:param trace_id: Unique trace id
:param span_id: Span Id of the current request span
:param parent_span_id: Parent span Id of the current request span
:param flags: stores flags header. Currently unused
:param is_sampled: pre-computed boolean whether the trace should be logged
"""
ZipkinAttrs = namedtuple(
    'ZipkinAttrs',
    ['trace_id', 'span_id', 'parent_span_id', 'flags', 'is_sampled'],
)

ERROR_KEY = 'error'

class zipkin_span(object):
    """Context manager/decorator for all of your zipkin tracing needs.

    Usage #1: Start a trace with a given sampling rate

    This begins the zipkin trace and also records the root span. The required
    params are service_name, transport_handler, and sample_rate.

    # Start a trace with do_stuff() as the root span
    def some_batch_job(a, b):
        with zipkin_span(
            service_name='my_service',
            span_name='my_span_name',
            transport_handler=some_handler,
            port=22,
            sample_rate=0.05,
        ):
            do_stuff()

    Usage #2: Trace a service call.

    The typical use case is instrumenting a framework like Pyramid or Django. Only
    ss and sr times are recorded for the root span. Required params are
    service_name, zipkin_attrs, transport_handler, and port.

    # Used in a pyramid tween
    def tween(request):
        zipkin_attrs = some_zipkin_attr_creator(request)
        with zipkin_span(
            service_name='my_service,'
            span_name='my_span_name',
            zipkin_attrs=zipkin_attrs,
            transport_handler=some_handler,
            port=22,
        ) as zipkin_context:
            response = handler(request)
            zipkin_context.update_binary_annotations(
                some_binary_annotations)
            return response

    Usage #3: Log a span within the context of a zipkin trace

    If you're already in a zipkin trace, you can use this to log a span inside. The
    only required param is service_name. If you're not in a zipkin trace, this
    won't do anything.

    # As a decorator
    @zipkin_span(service_name='my_service', span_name='my_function')
    def my_function():
        do_stuff()

    # As a context manager
    def my_function():
        with zipkin_span(service_name='my_service', span_name='do_stuff'):
            do_stuff()
    """

    def __init__(
        self,
        service_name,
        span_name='span',
        zipkin_attrs=None,
        transport_handler=None,
        max_span_batch_size=None,
        annotations=None,
        binary_annotations=None,
        port=0,
        sample_rate=None,
        include=None,
        add_logging_annotation=False,
        report_root_timestamp=False,
        use_128bit_trace_id=False,
        host=None,
        context_stack=None,
        span_storage=None,
        firehose_handler=None,
        kind=None,
        timestamp=None,
        duration=None,
        encoding=Encoding.V1_THRIFT,
        _tracer=None,
    ):
        """Logs a zipkin span. If this is the root span, then a zipkin
        trace is started as well.

        :param service_name: The name of the called service
        :type service_name: string
        :param span_name: Optional name of span, defaults to 'span'
        :type span_name: string
        :param zipkin_attrs: Optional set of zipkin attributes to be used
        :type zipkin_attrs: ZipkinAttrs
        :param transport_handler: Callback function that takes a message parameter
            and handles logging it
        :type transport_handler: BaseTransportHandler
        :param max_span_batch_size: Spans in a trace are sent in batches,
            max_span_batch_size defines max size of one batch
        :type max_span_batch_size: int
        :param annotations: Optional dict of str -> timestamp annotations
        :type annotations: dict of str -> int
        :param binary_annotations: Optional dict of str -> str span attrs
        :type binary_annotations: dict of str -> str
        :param port: The port number of the service. Defaults to 0.
        :type port: int
        :param sample_rate: Rate at which to sample; 0.0 - 100.0. If passed-in
            zipkin_attrs have is_sampled=False and the sample_rate param is > 0,
            a new span will be generated at this rate. This means that if you
            propagate sampling decisions to downstream services, but still have
            sample_rate > 0 in those services, the actual rate of generated
            spans for those services will be > sampling_rate.
        :type sample_rate: float
        :param include: which annotations to include
            can be one of {'client', 'server'}
            corresponding to ('cs', 'cr') and ('ss', 'sr') respectively.
            DEPRECATED: use kind instead. `include` will be removed in 1.0.
        :type include: iterable
        :param add_logging_annotation: Whether to add a 'logging_end'
            annotation when py_zipkin finishes logging spans
        :type add_logging_annotation: boolean
        :param report_root_timestamp: Whether the span should report timestamp
            and duration. Only applies to "root" spans in this local context,
            so spans created inside other span contexts will always log
            timestamp/duration. Note that this is only an override for spans
            that have zipkin_attrs passed in. Spans that make their own
            sampling decisions (i.e. are the root spans of entire traces) will
            always report timestamp/duration.
        :type report_root_timestamp: boolean
        :param use_128bit_trace_id: If true, generate 128-bit trace_ids.
        :type use_128bit_trace_id: boolean
        :param host: Contains the ipv4 or ipv6 value of the host. The ip value
            isn't automatically determined in a docker environment.
        :type host: string
        :param context_stack: explicit context stack for storing
            zipkin attributes
        :type context_stack: object
        :param span_storage: explicit Span storage for storing zipkin spans
            before they're emitted.
        :type span_storage: py_zipkin.storage.SpanStorage
        :param firehose_handler: [EXPERIMENTAL] Similar to transport_handler,
            except that it will receive 100% of the spans regardless of trace
            sampling rate.
        :type firehose_handler: BaseTransportHandler
        :param kind: Span type (client, server, local, etc...).
        :type kind: Kind
        :param timestamp: Timestamp in seconds, defaults to `time.time()`.
            Set this if you want to use a custom timestamp.
        :type timestamp: float
        :param duration: Duration in seconds, defaults to the time spent in the
            context. Set this if you want to use a custom duration.
        :type duration: float
        :param encoding: Output encoding format, defaults to V1 thrift spans.
        :type encoding: Encoding
        :param _tracer: Current tracer object. This argument is passed in
            automatically when you create a zipkin_span from a Tracer.
        :type _tracer: Tracer
        """
        self.service_name = service_name
        self.span_name = span_name
        self.zipkin_attrs_override = zipkin_attrs
        self.transport_handler = transport_handler
        self.max_span_batch_size = max_span_batch_size
        self.annotations = annotations or {}
        self.binary_annotations = binary_annotations or {}
        self.port = port
        self.sample_rate = sample_rate
        self.add_logging_annotation = add_logging_annotation
        self.report_root_timestamp_override = report_root_timestamp
        self.use_128bit_trace_id = use_128bit_trace_id
        self.host = host
        self._context_stack = context_stack
        self._span_storage = span_storage
        self.firehose_handler = firehose_handler
        self.kind = self._generate_kind(kind, include)
        self.timestamp = timestamp
        self.duration = duration
        self.encoding = encoding
        self._tracer = _tracer

        self._is_local_root_span = False
        self.logging_context = None
        self.do_pop_attrs = False
        # Spans that log a 'cs' timestamp can additionally record a
        # 'sa' binary annotation that shows where the request is going.
        self.remote_endpoint = None
        self.zipkin_attrs = None

        # It used to  be possible to override timestamp and duration by passing
        # in the cs/cr or sr/ss annotations. We want to keep backward compatibility
        # for now, so this logic overrides self.timestamp and self.duration in the
        # same way.
        # This doesn't fit well with v2 spans since those annotations are gone, so
        # we also log a deprecation warning.
        if 'sr' in self.annotations and 'ss' in self.annotations:
            self.duration = self.annotations['ss'] - self.annotations['sr']
            self.timestamp = self.annotations['sr']
            log.warning(
                "Manually setting 'sr'/'ss' annotations is deprecated. Please "
                "use the timestamp and duration parameters."
            )
        if 'cr' in self.annotations and 'cs' in self.annotations:
            self.duration = self.annotations['cr'] - self.annotations['cs']
            self.timestamp = self.annotations['cs']
            log.warning(
                "Manually setting 'cr'/'cs' annotations is deprecated. Please "
                "use the timestamp and duration parameters."
            )

        # Root spans have transport_handler and at least one of
        # zipkin_attrs_override or sample_rate.
        if self.zipkin_attrs_override or self.sample_rate is not None:
            # transport_handler is mandatory for root spans
            if self.transport_handler is None:
                raise ZipkinError(
                    'Root spans require a transport handler to be given')

            self._is_local_root_span = True

        # If firehose_handler than this is a local root span.
        if self.firehose_handler:
            self._is_local_root_span = True

        if self.sample_rate is not None and not (0.0 <= self.sample_rate <= 100.0):
            raise ZipkinError('Sample rate must be between 0.0 and 100.0')

        if self._span_storage is not None and \
                not isinstance(self._span_storage, storage.SpanStorage):
            raise ZipkinError('span_storage should be an instance '
                              'of py_zipkin.storage.SpanStorage')

        if self._span_storage is not None:
            log.warning('span_storage is deprecated. Set local_storage instead.')
            self.get_tracer()._span_storage = self._span_storage

        if self._context_stack is not None:
            log.warning('context_stack is deprecated. Set local_storage instead.')
            self.get_tracer()._context_stack = self._context_stack

    def __call__(self, f):
        @functools.wraps(f)
        def decorated(*args, **kwargs):
            with zipkin_span(
                service_name=self.service_name,
                span_name=self.span_name,
                zipkin_attrs=self.zipkin_attrs,
                transport_handler=self.transport_handler,
                max_span_batch_size=self.max_span_batch_size,
                annotations=self.annotations,
                binary_annotations=self.binary_annotations,
                port=self.port,
                sample_rate=self.sample_rate,
                include=None,
                add_logging_annotation=self.add_logging_annotation,
                report_root_timestamp=self.report_root_timestamp_override,
                use_128bit_trace_id=self.use_128bit_trace_id,
                host=self.host,
                context_stack=self._context_stack,
                span_storage=self._span_storage,
                firehose_handler=self.firehose_handler,
                kind=self.kind,
                timestamp=self.timestamp,
                duration=self.duration,
                encoding=self.encoding,
                _tracer=self._tracer,
            ):
                return f(*args, **kwargs)
        return decorated

    def get_tracer(self):
        if self._tracer is not None:
            return self._tracer
        else:
            return get_default_tracer()

    def __enter__(self):
        return self.start()

    def _generate_kind(self, kind, include):
        # If `kind` is not set, then we generate it from `include`.
        # This code maintains backward compatibility with old versions of py_zipkin
        # which used include rather than kind to identify client / server spans.
        if kind:
            return kind
        else:
            if include:
                # If `include` contains only one of `client` or `server`
                # than it's a client or server span respectively.
                # If neither or both are present, then it's a local span
                # which is represented by kind = None.
                log.warning(
                    'The include argument is deprecated. Please use kind.'
                )
                if 'client' in include and 'server' not in include:
                    return Kind.CLIENT
                elif 'client' not in include and 'server' in include:
                    return Kind.SERVER
                else:
                    return Kind.LOCAL

        # If both kind and include are unset, then it's a local span.
        return Kind.LOCAL

    def _get_current_context(self):
        """Returns the current ZipkinAttrs and generates new ones if needed.

        :returns: (report_root_timestamp, zipkin_attrs)
        :rtype: (bool, ZipkinAttrs)
        """
        # This check is technically not necessary since only root spans will have
        # sample_rate, zipkin_attrs or a transport set. But it helps making the
        # code clearer by separating the logic for a root span from the one for a
        # child span.
        if self._is_local_root_span:

            # If sample_rate is set, we need to (re)generate a trace context.
            # If zipkin_attrs (trace context) were passed in as argument there are
            # 2 possibilities:
            # is_sampled = False --> we keep the same trace_id but re-roll the dice
            #                        for is_sampled.
            # is_sampled = True  --> we don't want to stop sampling halfway through
            #                        a sampled trace, so we do nothing.
            # If no zipkin_attrs were passed in, we generate new ones and start a
            # new trace.
            if self.sample_rate is not None:

                # If this trace is not sampled, we re-roll the dice.
                if self.zipkin_attrs_override and \
                        not self.zipkin_attrs_override.is_sampled:
                    # This will be the root span of the trace, so we should
                    # set timestamp and duration.
                    return True, create_attrs_for_span(
                        sample_rate=self.sample_rate,
                        trace_id=self.zipkin_attrs_override.trace_id,
                    )

                # If zipkin_attrs_override was not passed in, we simply generate
                # new zipkin_attrs to start a new trace.
                elif not self.zipkin_attrs_override:
                    return True, create_attrs_for_span(
                        sample_rate=self.sample_rate,
                        use_128bit_trace_id=self.use_128bit_trace_id,
                    )

            if self.firehose_handler and not self.zipkin_attrs_override:
                # If it has gotten here, the only thing that is
                # causing a trace is the firehose. So we force a trace
                # with sample rate of 0
                return True, create_attrs_for_span(
                    sample_rate=0.0,
                    use_128bit_trace_id=self.use_128bit_trace_id,
                )

            # If we arrive here it means the sample_rate was not set while
            # zipkin_attrs_override was, so let's simply return that.
            return False, self.zipkin_attrs_override

        else:
            # Check if there's already a trace context in _context_stack.
            existing_zipkin_attrs = self.get_tracer().get_zipkin_attrs()
            # If there's an existing context, let's create new zipkin_attrs
            # with that context as parent.
            if existing_zipkin_attrs:
                return False, ZipkinAttrs(
                    trace_id=existing_zipkin_attrs.trace_id,
                    span_id=generate_random_64bit_string(),
                    parent_span_id=existing_zipkin_attrs.span_id,
                    flags=existing_zipkin_attrs.flags,
                    is_sampled=existing_zipkin_attrs.is_sampled,
                )

        return False, None

    def start(self):
        """Enter the new span context. All annotations logged inside this
        context will be attributed to this span. All new spans generated
        inside this context will have this span as their parent.

        In the unsampled case, this context still generates new span IDs and
        pushes them onto the threadlocal stack, so downstream services calls
        made will pass the correct headers. However, the logging handler is
        never attached in the unsampled case, so the spans are never logged.
        """
        self.do_pop_attrs = False

        report_root_timestamp, self.zipkin_attrs = self._get_current_context()

        # If zipkin_attrs are not set up by now, that means this span is not
        # configured to perform logging itself, and it's not in an existing
        # Zipkin trace. That means there's nothing else to do and it can exit
        # early.
        if not self.zipkin_attrs:
            return self

        self.get_tracer().push_zipkin_attrs(self.zipkin_attrs)
        self.do_pop_attrs = True

        self.start_timestamp = time.time()

        if self._is_local_root_span:
            # Don't set up any logging if we're not sampling
            if not self.zipkin_attrs.is_sampled and not self.firehose_handler:
                return self
            # If transport is already configured don't override it. Doing so would
            # cause all previously recorded spans to never be emitted as exiting
            # the inner logging context will reset transport_configured to False.
            if self.get_tracer().is_transport_configured():
                log.info('Transport was already configured, ignoring override'
                         'from span {}'.format(self.span_name))
                return self
            endpoint = create_endpoint(self.port, self.service_name, self.host)
            self.logging_context = ZipkinLoggingContext(
                self.zipkin_attrs,
                endpoint,
                self.span_name,
                self.transport_handler,
                report_root_timestamp or self.report_root_timestamp_override,
                self.get_tracer,
                self.service_name,
                binary_annotations=self.binary_annotations,
                add_logging_annotation=self.add_logging_annotation,
                client_context=self.kind == Kind.CLIENT,
                max_span_batch_size=self.max_span_batch_size,
                firehose_handler=self.firehose_handler,
                encoding=self.encoding,
            )
            self.logging_context.start()
            self.get_tracer().set_transport_configured(configured=True)

        return self

    def __exit__(self, _exc_type, _exc_value, _exc_traceback):
        self.stop(_exc_type, _exc_value, _exc_traceback)

    def stop(self, _exc_type=None, _exc_value=None, _exc_traceback=None):
        """Exit the span context. Zipkin attrs are pushed onto the
        threadlocal stack regardless of sampling, so they always need to be
        popped off. The actual logging of spans depends on sampling and that
        the logging was correctly set up.
        """

        if self.do_pop_attrs:
            self.get_tracer().pop_zipkin_attrs()

        # If no transport is configured, there's no reason to create a new Span.
        # This also helps avoiding memory leaks since without a transport nothing
        # would pull spans out of get_tracer().
        if not self.get_tracer().is_transport_configured():
            return

        # Add the error annotation if an exception occurred
        if any((_exc_type, _exc_value, _exc_traceback)):
            error_msg = u'{0}: {1}'.format(_exc_type.__name__, _exc_value)
            self.update_binary_annotations({
                ERROR_KEY: error_msg,
            })

        # Logging context is only initialized for "root" spans of the local
        # process (i.e. this zipkin_span not inside of any other local
        # zipkin_spans)
        if self.logging_context:
            try:
                self.logging_context.stop()
            except Exception as ex:
                err_msg = 'Error emitting zipkin trace. {}'.format(
                    repr(ex),
                )
                log.error(err_msg)
            finally:
                self.logging_context = None
                self.get_tracer().clear()
                self.get_tracer().set_transport_configured(configured=False)
                return

        # If we've gotten here, that means that this span is a child span of
        # this context's root span (i.e. it's a zipkin_span inside another
        # zipkin_span).
        end_timestamp = time.time()
        # If self.duration is set, it means the user wants to override it
        if self.duration:
            duration = self.duration
        else:
            duration = end_timestamp - self.start_timestamp

        endpoint = create_endpoint(self.port, self.service_name, self.host)
        self.get_tracer().add_span(Span(
            trace_id=self.zipkin_attrs.trace_id,
            name=self.span_name,
            parent_id=self.zipkin_attrs.parent_span_id,
            span_id=self.zipkin_attrs.span_id,
            kind=self.kind,
            timestamp=self.timestamp if self.timestamp else self.start_timestamp,
            duration=duration,
            annotations=self.annotations,
            local_endpoint=endpoint,
            remote_endpoint=self.remote_endpoint,
            tags=self.binary_annotations,
        ))

    def update_binary_annotations(self, extra_annotations):
        """Updates the binary annotations for the current span."""
        if not self.logging_context:
            # This is not the root span, so binary annotations will be added
            # to the log handler when this span context exits.
            self.binary_annotations.update(extra_annotations)
        else:
            # Otherwise, we're in the context of the root span, so just update
            # the binary annotations for the logging context directly.
            self.logging_context.tags.update(extra_annotations)

    def add_sa_binary_annotation(
        self,
        port=0,
        service_name='unknown',
        host='127.0.0.1',
    ):
        """Adds a 'sa' binary annotation to the current span.

        'sa' binary annotations are useful for situations where you need to log
        where a request is going but the destination doesn't support zipkin.

        Note that the span must have 'cs'/'cr' annotations.

        :param port: The port number of the destination
        :type port: int
        :param service_name: The name of the destination service
        :type service_name: str
        :param host: Host address of the destination
        :type host: str
        """
        if self.kind != Kind.CLIENT:
            # TODO: trying to set a sa binary annotation for a non-client span
            # should result in a logged error
            return

        remote_endpoint = create_endpoint(
            port=port,
            service_name=service_name,
            host=host,
        )
        if not self.logging_context:
            if self.remote_endpoint is not None:
                raise ValueError('SA annotation already set.')
            self.remote_endpoint = remote_endpoint
        else:
            if self.logging_context.remote_endpoint is not None:
                raise ValueError('SA annotation already set.')
            self.logging_context.remote_endpoint = remote_endpoint

    def override_span_name(self, name):
        """Overrides the current span name.

        This is useful if you don't know the span name yet when you create the
        zipkin_span object. i.e. pyramid_zipkin doesn't know which route the
        request matched until the function wrapped by the context manager
        completes.

        :param name: New span name
        :type name: str
        """
        self.span_name = name
        if self.logging_context:
            self.logging_context.span_name = name

def _validate_args(kwargs):
    if 'kind' in kwargs:
        raise ValueError(
            '"kind" is not valid in this context. '
            'You probably want to use zipkin_span()'
        )

class zipkin_client_span(zipkin_span):
    """Logs a client-side zipkin span.

    Subclass of :class:`zipkin_span` using only annotations relevant to clients
    """

    def __init__(self, *args, **kwargs):
        """Logs a zipkin span with client annotations.

        See :class:`zipkin_span` for arguments
        """
        _validate_args(kwargs)

        kwargs['kind'] = Kind.CLIENT
        super(zipkin_client_span, self).__init__(*args, **kwargs)

class zipkin_server_span(zipkin_span):
    """Logs a server-side zipkin span.

    Subclass of :class:`zipkin_span` using only annotations relevant to servers
    """

    def __init__(self, *args, **kwargs):
        """Logs a zipkin span with server annotations.

        See :class:`zipkin_span` for arguments
        """
        _validate_args(kwargs)

        kwargs['kind'] = Kind.SERVER
        super(zipkin_server_span, self).__init__(*args, **kwargs)

def create_attrs_for_span(
    sample_rate=100.0,
    trace_id=None,
    span_id=None,
    use_128bit_trace_id=False,
):
    """Creates a set of zipkin attributes for a span.

    :param sample_rate: Float between 0.0 and 100.0 to determine sampling rate
    :type sample_rate: float
    :param trace_id: Optional 16-character hex string representing a trace_id.
                    If this is None, a random trace_id will be generated.
    :type trace_id: str
    :param span_id: Optional 16-character hex string representing a span_id.
                    If this is None, a random span_id will be generated.
    :type span_id: str
    :param use_128bit_trace_id: If true, generate 128-bit trace_ids
    :type use_128bit_trace_id: boolean
    """
    # Calculate if this trace is sampled based on the sample rate
    if trace_id is None:
        if use_128bit_trace_id:
            trace_id = generate_random_128bit_string()
        else:
            trace_id = generate_random_64bit_string()
    if span_id is None:
        span_id = generate_random_64bit_string()
    if sample_rate == 0.0:
        is_sampled = False
    else:
        is_sampled = (random.random() * 100) < sample_rate

    return ZipkinAttrs(
        trace_id=trace_id,
        span_id=span_id,
        parent_span_id=None,
        flags='0',
        is_sampled=is_sampled,
    )

def create_http_headers_for_new_span(context_stack=None, tracer=None):
    """
    Generate the headers for a new zipkin span.

    .. note::

        If the method is not called from within a zipkin_trace context,
        empty dict will be returned back.

    :returns: dict containing (X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId,
                X-B3-Flags and X-B3-Sampled) keys OR an empty dict.
    """
    if tracer:
        zipkin_attrs = tracer.get_zipkin_attrs()
    elif context_stack:
        zipkin_attrs = context_stack.get()
    else:
        zipkin_attrs = get_default_tracer().get_zipkin_attrs()

    if not zipkin_attrs:
        return {}

    return {
        'X-B3-TraceId': zipkin_attrs.trace_id,
        'X-B3-SpanId': generate_random_64bit_string(),
        'X-B3-ParentSpanId': zipkin_attrs.span_id,
        'X-B3-Flags': '0',
        'X-B3-Sampled': '1' if zipkin_attrs.is_sampled else '0',
    }


# -*- coding: utf-8 -*-
import socket
from collections import namedtuple
from collections import OrderedDict

from py_zipkin.encoding._types import Kind
from py_zipkin.exception import ZipkinError

Endpoint = namedtuple(
    'Endpoint',
    ['service_name', 'ipv4', 'ipv6', 'port'],
)

_V1Span = namedtuple(
    'V1Span',
    ['trace_id', 'name', 'parent_id', 'id', 'timestamp', 'duration', 'endpoint',
     'annotations', 'binary_annotations', 'remote_endpoint'],
)

_DROP_ANNOTATIONS_BY_KIND = {
    Kind.CLIENT: {'ss', 'sr'},
    Kind.SERVER: {'cs', 'cr'},
}

class Span(object):
    """Internal V2 Span representation."""

    def __init__(
        self,
        trace_id,
        name,
        parent_id,
        span_id,
        kind,
        timestamp,
        duration,
        local_endpoint=None,
        remote_endpoint=None,
        debug=False,
        shared=False,
        annotations=None,
        tags=None,
    ):
        """Creates a new Span.

        :param trace_id: Trace id.
        :type trace_id: str
        :param name: Name of the span.
        :type name: str
        :param parent_id: Parent span id.
        :type parent_id: str
        :param span_id: Span id.
        :type span_id: str
        :param kind: Span type (client, server, local, etc...)
        :type kind: Kind
        :param timestamp: start timestamp in seconds.
        :type timestamp: float
        :param duration: span duration in seconds.
        :type duration: float
        :param local_endpoint: the host that recorded this span.
        :type local_endpoint: Endpoint
        :param remote_endpoint: the remote service.
        :type remote_endpoint: Endpoint
        :param debug: True is a request to store this span even if it
            overrides sampling policy.
        :type debug: bool
        :param shared: True if we are contributing to a span started by
            another tracer (ex on a different host).
        :type shared: bool
        :param annotations: Optional dict of str -> timestamp annotations.
        :type annotations: dict
        :param tags: Optional dict of str -> str span tags.
        :type tags: dict
        """
        self.trace_id = trace_id
        self.name = name
        self.parent_id = parent_id
        self.span_id = span_id
        self.kind = kind
        self.timestamp = timestamp
        self.duration = duration
        self.local_endpoint = local_endpoint
        self.remote_endpoint = remote_endpoint
        self.debug = debug
        self.shared = shared
        self.annotations = annotations or {}
        self.tags = tags or {}

        if not isinstance(kind, Kind):
            raise ZipkinError(
                'Invalid kind value {}. Must be of type Kind.'.format(kind))

        if local_endpoint and not isinstance(local_endpoint, Endpoint):
            raise ZipkinError(
                'Invalid local_endpoint value. Must be of type Endpoint.')

        if remote_endpoint and not isinstance(remote_endpoint, Endpoint):
            raise ZipkinError(
                'Invalid remote_endpoint value. Must be of type Endpoint.')

    def __eq__(self, other):  # pragma: no cover
        """Compare function to help assert span1 == span2 in py3"""
        return self.__dict__ == other.__dict__

    def __cmp__(self, other):  # pragma: no cover
        """Compare function to help assert span1 == span2 in py2"""
        return self.__dict__ == other.__dict__

    def __str__(self):  # pragma: no cover
        """Compare function to nicely print Span rather than just the pointer"""
        return str(self.__dict__)

    def build_v1_span(self):
        """Builds and returns a V1 Span.

        :return: newly generated _V1Span
        :rtype: _V1Span
        """
        # We are simulating a full two-part span locally, so set cs=sr and ss=cr
        full_annotations = OrderedDict([
            ('cs', self.timestamp),
            ('sr', self.timestamp),
            ('ss', self.timestamp + self.duration),
            ('cr', self.timestamp + self.duration),
        ])

        if self.kind != Kind.LOCAL:
            # If kind is not LOCAL, then we only want client or
            # server side annotations.
            for ann in _DROP_ANNOTATIONS_BY_KIND[self.kind]:
                del full_annotations[ann]

        # Add user-defined annotations. We write them in full_annotations
        # instead of the opposite so that user annotations will override
        # any automatically generated annotation.
        full_annotations.update(self.annotations)

        return _V1Span(
            trace_id=self.trace_id,
            name=self.name,
            parent_id=self.parent_id,
            id=self.span_id,
            timestamp=self.timestamp if self.shared is False else None,
            duration=self.duration if self.shared is False else None,
            endpoint=self.local_endpoint,
            annotations=full_annotations,
            binary_annotations=self.tags,
            remote_endpoint=self.remote_endpoint,
        )

def create_endpoint(port=None, service_name=None, host=None, use_defaults=True):
    """Creates a new Endpoint object.

    :param port: TCP/UDP port. Defaults to 0.
    :type port: int
    :param service_name: service name as a str. Defaults to 'unknown'.
    :type service_name: str
    :param host: ipv4 or ipv6 address of the host. Defaults to the
    current host ip.
    :type host: str
    :param use_defaults: whether to use defaults.
    :type use_defaults: bool
    :returns: zipkin Endpoint object
    """
    if use_defaults:
        if port is None:
            port = 0
        if service_name is None:
            service_name = 'unknown'
        if host is None:
            try:
                host = socket.gethostbyname(socket.gethostname())
            except socket.gaierror:
                host = '127.0.0.1'

    ipv4 = None
    ipv6 = None

    if host:
        # Check ipv4 or ipv6.
        try:
            socket.inet_pton(socket.AF_INET, host)
            ipv4 = host
        except socket.error:
            # If it's not an ipv4 address, maybe it's ipv6.
            try:
                socket.inet_pton(socket.AF_INET6, host)
                ipv6 = host
            except socket.error:
                # If it's neither ipv4 or ipv6, leave both ip addresses unset.
                pass

    return Endpoint(
        ipv4=ipv4,
        ipv6=ipv6,
        port=port,
        service_name=service_name,
    )

def copy_endpoint_with_new_service_name(endpoint, new_service_name):
    """Creates a copy of a given endpoint with a new service name.

    :param endpoint: existing Endpoint object
    :type endpoint: Endpoint
    :param new_service_name: new service name
    :type new_service_name: str
    :returns: zipkin new Endpoint object
    """
    return Endpoint(
        service_name=new_service_name,
        ipv4=endpoint.ipv4,
        ipv6=endpoint.ipv6,
        port=endpoint.port,
    )