bb-apputils


Namebb-apputils JSON
Version 0.4.0 PyPI version JSON
download
home_page
SummaryApp utilities
upload_time2023-07-16 07:23:07
maintainer
docs_urlNone
authorErik Beebe
requires_python>=3.10,<4.0
license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # BB_Utils

## Utilities for running simple applications

#### Includes:

- bbappdirs
    - a simple module for handling user app directories.
- bblogger
    - custom Logger and Formatter class for the built-in python logging module
- bbargparser
    - parse command line arguments with options

## BBLogger

> Custom logging classes

- Subclasses of logging.Logger and logging.Formatter

```python

class BBLogger(logging.getLoggerClass()):
    """
    Console and file logging, formatted with BBFormatter
        - options are set through logger.getLogger() with initial call
        - subsequent loggers should be called with python's logging
          module: logging.getLogger()
    """

  # Added functions

    def set_level(self, level):
        """
        Sets level for current and all other console loggers
            - files will always be debugging mode
            - acceptable modes:
                'debug'    | logging.DEBUG    | 10 | 1 <or> 0
                'info'     | logging.INFO     | 20 | 2
                'warning'  | logging.WARNING  | 30 | 3
                'error'    | logging.ERROR    | 40 | 4
                'critical' | logging.CRITICAL | 50 | 5
        """

    def set_format(self, formatting):
        """
        Change formatting for console logging
            'basic' - simple, nicely formatted messaging
            'debug' - more info pertaining to each message
                      * defaults to log level 1
        """

```

## BBFormatter

> Make logs pretty

```python

class BBFormatter(logging.Formatter):
    """
    BBFormatter - Color logging output with ansi or html

        mode = 'basic' (console)
                Basic logging for end user messages

               'debug' (console)
                More output information for debugging

               'html' (file)
                Uses html format to color logs to send to an html file or gui
                program that supports html formatting

               'plaintext' (file)
                No escapes inserted for logging plaintext to a file
    """

```

#### logger.getLogger()

> Not to be confused with python's logging.getLogger(). Use this method for the original logger call ( during __init__.py for example ), then make all subsequent calls to get loggers with python's built-in logging.getLogger() method.

```python

def getLogger( name, level = 1, **opts ):
    """
    Set custom logger class and return logger
      - only use this for initial call to logger. Use logging.getLogger() for
        further logging modules

        'name'    = Name of returned logger
        'level'   = Log level for the returned logger. Defaults to 1.
\
        **opts:
              More options for logger. To print the log to a file, 'filepath'
            must be present in the opts.

            'appname'      : [ DEFAULT 'BB-Logger' ] Application name
            'console'      : [ DEFAULT = True ] Print logging to console
            'consoleformat': [ DEFAULT = 'basic' ] Console formatting. Options are
                                'basic' or 'debug'.
            'color'        : [ DEFAULT = True ] colorized console logging
            'filepath'     : [ DEFAULT None ] The path for the file to be written to.
                                The directory for the file must exist. If the file
                                exists and write_mode 'w' is used, log file will be
                                overwritten.
            'write_mode'   : [ DEFAULT = 'a'] Write mode - ('a', 'append', 'w', 'write')
            'filelevel'    : [ DEFAULT = 1] Set log level for file. Default is 1, DEBUGGING
                                - must be set with initial call to logger.getLogger()
            'fileformat'   : [ DEFAULT = 'html' ] Text formatting for file - 'plaintext'
                                or 'html'

              A new file will be created if not existing as long as the directory
            already exists. If only a filename is given for 'path', the file will
            be written in the user's HOME folder. Extra options should only need
            applied for the first time initiating a logger in your app/script unless
            logfile changes are wanted for a particular logger. The 'color' option
            only applies to console output.
    """

```

### Example usage:

```python

# __init__.py

from bb_logger import logger
log = logger.getLogger( __name__, 3,
                        appname   = "My Awesome Application",
                        filepath  = "/path/to/logfile",
                        filelevel = 1 )

# -------------------------------------------------------------

# __main__.py

import logging
log = logging.getLogger(__name__)

```

## BBAppDirs

> Locates and creates application directories as needed according to application name. Keeps a temporary file while app is running to assure the same files are found during a user's session, even with multiple calls from different modules.

```python

class AppDirs:
    """
    Get system specific app directories
        - for initiating app configurations

        A temporary file is created and read for reading by other
      modules within the app.

      Module Data > 'expire' : expiration time in seconds
                    'name'   : application name
                    'logfile': logfile path
                    'logs'   : list of stored logs from SimpleLog
                    'time'   : initial AppDirs call time
                    'tmp'    : temporary file path
                    'shared' : shared dictionary data


        self.data['logs'] = [{ 'levelname': str(),
                               'level'    : int()
                               'time'     : datetime.timestamp(),
                               'msg'      : str(),
                               'formatted': str()  },
                                etc...  ]

    """

    def __init__( self, *, name = "", expire = 0, unique = "", simplelog = False, loglevel = 0, noerrors = False, shared = {} ):
        """
        name = provide application name
            - required
        expire = set an expiration time in seconds
            - temporary file is abandoned/deleted if older than this time
            - expire <= 0 = no expiration
            - default = 0
        unique = provide a name or unique set of characters
            - prevent other programs or modules from unintentionally reading
              or deleting the wrong temp files
            - required with every call that wants to access this data
        simplelog = use SimpleLog instead of python's built-in logging module
            - default False
        loglevel = log level for SimpleLog
            - ignored for python's logging module
        noerrors = don't print logs at exit due to errors
            - only applies if loglevel not set for SimpleLog
        shared = data to share throughout the application
            - must be in dictionary format

            SimpleLog is used for initial call. Subsequent calls, if the temp file
          is created, will load python's built-in logging module unless 'simplelog'
          is set to True.

        """

```

#### SimpleLog

> Includes a simple log class to use on initial call to AppDirs. You can optionally continue to only use SimpleLog if you set the kwarg 'simplelog' to True in AppDirs.

```python

class SimpleLog:
    """
    SimpleLog

      A simple logger. Is used during initial call to AppDirs to give the application
    the chance to initiate python's logging module before loading it here.
    """

    def __init__(self, level = 0, init = False, *, log_to_data):
        """
        Initiate SimpleLog
            - level = 0: off [default]
                      1: debug
                      2: info
                      3: warning
                      4: error
                      5: critical

            - init = If True, will register self.onExit() with atexit. If log level is
                     set to 0 [off], all log messages will be printed after exiting if
                     any errors [>4] were recorded.

            - log_to_data = callback function to write log data to temp file

            Similar to the standard built-in logging, messages from set level and above
          will be displayed. Level set to 0 will turn SimpleLog off. This does not effect
          python's built-in logging module.

        """

```

## BBArgParser

```python

from bbargparser import ArgParser

# ------------------------------------

class ArgParser(dict):
    """
    ArgParser
      - multiple ways to parse options

        opts = list of tuples

        Each option can have as many keywords as wanted. Options are set up in
      a Unix style. No hyphens are necessary when initiating accepted options. In
      fact, they will be stripped from the beginning of each option and reassigned
      appropriately if existing.

        Options are given in a list/tuple. Each accepted option, both short and long
      arguments, are given in the same tuple. Single letter options, short opts, will
      be prepended with a single hyphen '-'. Long options with two hyphens '--'.

        You can specify acceptable options or types as well for each keyword/group of
      keywords. To do this, include '_+_' in the same tuple. Anything following this
      value will symbolize an accepted user argument or argument type. You can specify
      a path, file, or directory with '__path__', '__file__', or '__directory__'. The
      directory or file options will check to make sure that the file or directory exists
      as a file or directory. The path option only checks to make sure that the argument
      is in the proper form of a file path, ignoring whether it exists or not. The file
      path must be an absolute path.

        Another accepted option type is '__count__n', while n is the number of arguments
      required for that option. This is the only "accepted option" that you can combine
      with another type. Anything other than types will not be accepted in combination.
        For example:
            ArgParser( [('i', 'items', '_+_', '__count__3', str)] ) requires 3 arguments
          that are integers for the options '-i', '--items'.

        You can also use regex to define an accepted argument with '__regex__:' with the
      regex string following the colon (':'). Remember to use proper escapes. This option
      uses the built-in re module.

        The last "acceptable option" option is providing a function that returns True or
      False and accepts a single string argument. This option can only be used by itself.

        When specifying the type using the '_+_' value, any other arguments following it
      will be ignored, unless it's '__count__n', in which case anything after that's not
      a type will be ignored. If the user argument given doesn't match the type, a SyntaxError
      exception will be raised, unless the type is '__file__' or '__directory__', in which
      case a FileNotFoundError will be raised. An invalid option passed by the user will
      cause SyntaxError to be raised.
    """

# Example:
opts = ArgParser( [('i', 'input-file', '_+_', '__file__'),
                   ('o', 'output-file', '_+_', '__path__')] )

for opt, arg in opts( sys.argv[1:],
                      ignore_delimiters = True,
                      set_delimiter = None,
                      clear = True )

    """
        Options would be set as '-i', '--input-file' and '-o', '--output-file'. The '-i'
      option will only accept an existing file as an argument and the '-o' option will
      accept an argument that matches a path format, regardless of whether it exists. opts(...)
      will return a key, item list from the provided user arguments. The '.items()' function
      should not be used when calling the class with user arguments. If no arguments are given,
      self (dict) is returned. The default is to clear self every time it is called unless 'clear'
      is set to False.

    """

```

## Changelog

- 0.3.5
    - modified BBLogger and getLogger to hopefully maintain the right level across modules
    - added 'rootlogger' level option

- 0.3.8
    - changed formatter to include filename instead of logger name

- 0.3.9
    - fixed logging error when not in terminal

- 0.3.91
    - fixed typo in basic logging.... maybe

- 0.3.92
    - 2nd attempt at fixing basic logging

- 0.3.95
    - yet another attempt to get basic log formatting right

- 0.4.0
    - re-wrote the basic log formatter

## Thanks To:

##### Colored logging with logging.Formatter()

<p>Thread at <a href="https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output">Stack Overflow</a> on python log formatting</p>

<p>Adapted from <a href="https://stackoverflow.com/a/56944256/3638629">original code</a> by user's at Stack Overflow</p>

#### Thanks for providing help so people like me can learn :)

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "bb-apputils",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Erik Beebe",
    "author_email": "beebeapps_debugging@tuta.io",
    "download_url": "https://files.pythonhosted.org/packages/1f/69/072499274eaa09505e7aa5416d399441f9bc5f355d3a7c35a66f2b10fea3/bb_apputils-0.4.0.tar.gz",
    "platform": null,
    "description": "# BB_Utils\n\n## Utilities for running simple applications\n\n#### Includes:\n\n- bbappdirs\n    - a simple module for handling user app directories.\n- bblogger\n    - custom Logger and Formatter class for the built-in python logging module\n- bbargparser\n    - parse command line arguments with options\n\n## BBLogger\n\n> Custom logging classes\n\n- Subclasses of logging.Logger and logging.Formatter\n\n```python\n\nclass BBLogger(logging.getLoggerClass()):\n    \"\"\"\n    Console and file logging, formatted with BBFormatter\n        - options are set through logger.getLogger() with initial call\n        - subsequent loggers should be called with python's logging\n          module: logging.getLogger()\n    \"\"\"\n\n  # Added functions\n\n    def set_level(self, level):\n        \"\"\"\n        Sets level for current and all other console loggers\n            - files will always be debugging mode\n            - acceptable modes:\n                'debug'    | logging.DEBUG    | 10 | 1 <or> 0\n                'info'     | logging.INFO     | 20 | 2\n                'warning'  | logging.WARNING  | 30 | 3\n                'error'    | logging.ERROR    | 40 | 4\n                'critical' | logging.CRITICAL | 50 | 5\n        \"\"\"\n\n    def set_format(self, formatting):\n        \"\"\"\n        Change formatting for console logging\n            'basic' - simple, nicely formatted messaging\n            'debug' - more info pertaining to each message\n                      * defaults to log level 1\n        \"\"\"\n\n```\n\n## BBFormatter\n\n> Make logs pretty\n\n```python\n\nclass BBFormatter(logging.Formatter):\n    \"\"\"\n    BBFormatter - Color logging output with ansi or html\n\n        mode = 'basic' (console)\n                Basic logging for end user messages\n\n               'debug' (console)\n                More output information for debugging\n\n               'html' (file)\n                Uses html format to color logs to send to an html file or gui\n                program that supports html formatting\n\n               'plaintext' (file)\n                No escapes inserted for logging plaintext to a file\n    \"\"\"\n\n```\n\n#### logger.getLogger()\n\n> Not to be confused with python's logging.getLogger(). Use this method for the original logger call ( during __init__.py for example ), then make all subsequent calls to get loggers with python's built-in logging.getLogger() method.\n\n```python\n\ndef getLogger( name, level = 1, **opts ):\n    \"\"\"\n    Set custom logger class and return logger\n      - only use this for initial call to logger. Use logging.getLogger() for\n        further logging modules\n\n        'name'    = Name of returned logger\n        'level'   = Log level for the returned logger. Defaults to 1.\n\\\n        **opts:\n              More options for logger. To print the log to a file, 'filepath'\n            must be present in the opts.\n\n            'appname'      : [ DEFAULT 'BB-Logger' ] Application name\n            'console'      : [ DEFAULT = True ] Print logging to console\n            'consoleformat': [ DEFAULT = 'basic' ] Console formatting. Options are\n                                'basic' or 'debug'.\n            'color'        : [ DEFAULT = True ] colorized console logging\n            'filepath'     : [ DEFAULT None ] The path for the file to be written to.\n                                The directory for the file must exist. If the file\n                                exists and write_mode 'w' is used, log file will be\n                                overwritten.\n            'write_mode'   : [ DEFAULT = 'a'] Write mode - ('a', 'append', 'w', 'write')\n            'filelevel'    : [ DEFAULT = 1] Set log level for file. Default is 1, DEBUGGING\n                                - must be set with initial call to logger.getLogger()\n            'fileformat'   : [ DEFAULT = 'html' ] Text formatting for file - 'plaintext'\n                                or 'html'\n\n              A new file will be created if not existing as long as the directory\n            already exists. If only a filename is given for 'path', the file will\n            be written in the user's HOME folder. Extra options should only need\n            applied for the first time initiating a logger in your app/script unless\n            logfile changes are wanted for a particular logger. The 'color' option\n            only applies to console output.\n    \"\"\"\n\n```\n\n### Example usage:\n\n```python\n\n# __init__.py\n\nfrom bb_logger import logger\nlog = logger.getLogger( __name__, 3,\n                        appname   = \"My Awesome Application\",\n                        filepath  = \"/path/to/logfile\",\n                        filelevel = 1 )\n\n# -------------------------------------------------------------\n\n# __main__.py\n\nimport logging\nlog = logging.getLogger(__name__)\n\n```\n\n## BBAppDirs\n\n> Locates and creates application directories as needed according to application name. Keeps a temporary file while app is running to assure the same files are found during a user's session, even with multiple calls from different modules.\n\n```python\n\nclass AppDirs:\n    \"\"\"\n    Get system specific app directories\n        - for initiating app configurations\n\n        A temporary file is created and read for reading by other\n      modules within the app.\n\n      Module Data > 'expire' : expiration time in seconds\n                    'name'   : application name\n                    'logfile': logfile path\n                    'logs'   : list of stored logs from SimpleLog\n                    'time'   : initial AppDirs call time\n                    'tmp'    : temporary file path\n                    'shared' : shared dictionary data\n\n\n        self.data['logs'] = [{ 'levelname': str(),\n                               'level'    : int()\n                               'time'     : datetime.timestamp(),\n                               'msg'      : str(),\n                               'formatted': str()  },\n                                etc...  ]\n\n    \"\"\"\n\n    def __init__( self, *, name = \"\", expire = 0, unique = \"\", simplelog = False, loglevel = 0, noerrors = False, shared = {} ):\n        \"\"\"\n        name = provide application name\n            - required\n        expire = set an expiration time in seconds\n            - temporary file is abandoned/deleted if older than this time\n            - expire <= 0 = no expiration\n            - default = 0\n        unique = provide a name or unique set of characters\n            - prevent other programs or modules from unintentionally reading\n              or deleting the wrong temp files\n            - required with every call that wants to access this data\n        simplelog = use SimpleLog instead of python's built-in logging module\n            - default False\n        loglevel = log level for SimpleLog\n            - ignored for python's logging module\n        noerrors = don't print logs at exit due to errors\n            - only applies if loglevel not set for SimpleLog\n        shared = data to share throughout the application\n            - must be in dictionary format\n\n            SimpleLog is used for initial call. Subsequent calls, if the temp file\n          is created, will load python's built-in logging module unless 'simplelog'\n          is set to True.\n\n        \"\"\"\n\n```\n\n#### SimpleLog\n\n> Includes a simple log class to use on initial call to AppDirs. You can optionally continue to only use SimpleLog if you set the kwarg 'simplelog' to True in AppDirs.\n\n```python\n\nclass SimpleLog:\n    \"\"\"\n    SimpleLog\n\n      A simple logger. Is used during initial call to AppDirs to give the application\n    the chance to initiate python's logging module before loading it here.\n    \"\"\"\n\n    def __init__(self, level = 0, init = False, *, log_to_data):\n        \"\"\"\n        Initiate SimpleLog\n            - level = 0: off [default]\n                      1: debug\n                      2: info\n                      3: warning\n                      4: error\n                      5: critical\n\n            - init = If True, will register self.onExit() with atexit. If log level is\n                     set to 0 [off], all log messages will be printed after exiting if\n                     any errors [>4] were recorded.\n\n            - log_to_data = callback function to write log data to temp file\n\n            Similar to the standard built-in logging, messages from set level and above\n          will be displayed. Level set to 0 will turn SimpleLog off. This does not effect\n          python's built-in logging module.\n\n        \"\"\"\n\n```\n\n## BBArgParser\n\n```python\n\nfrom bbargparser import ArgParser\n\n# ------------------------------------\n\nclass ArgParser(dict):\n    \"\"\"\n    ArgParser\n      - multiple ways to parse options\n\n        opts = list of tuples\n\n        Each option can have as many keywords as wanted. Options are set up in\n      a Unix style. No hyphens are necessary when initiating accepted options. In\n      fact, they will be stripped from the beginning of each option and reassigned\n      appropriately if existing.\n\n        Options are given in a list/tuple. Each accepted option, both short and long\n      arguments, are given in the same tuple. Single letter options, short opts, will\n      be prepended with a single hyphen '-'. Long options with two hyphens '--'.\n\n        You can specify acceptable options or types as well for each keyword/group of\n      keywords. To do this, include '_+_' in the same tuple. Anything following this\n      value will symbolize an accepted user argument or argument type. You can specify\n      a path, file, or directory with '__path__', '__file__', or '__directory__'. The\n      directory or file options will check to make sure that the file or directory exists\n      as a file or directory. The path option only checks to make sure that the argument\n      is in the proper form of a file path, ignoring whether it exists or not. The file\n      path must be an absolute path.\n\n        Another accepted option type is '__count__n', while n is the number of arguments\n      required for that option. This is the only \"accepted option\" that you can combine\n      with another type. Anything other than types will not be accepted in combination.\n        For example:\n            ArgParser( [('i', 'items', '_+_', '__count__3', str)] ) requires 3 arguments\n          that are integers for the options '-i', '--items'.\n\n        You can also use regex to define an accepted argument with '__regex__:' with the\n      regex string following the colon (':'). Remember to use proper escapes. This option\n      uses the built-in re module.\n\n        The last \"acceptable option\" option is providing a function that returns True or\n      False and accepts a single string argument. This option can only be used by itself.\n\n        When specifying the type using the '_+_' value, any other arguments following it\n      will be ignored, unless it's '__count__n', in which case anything after that's not\n      a type will be ignored. If the user argument given doesn't match the type, a SyntaxError\n      exception will be raised, unless the type is '__file__' or '__directory__', in which\n      case a FileNotFoundError will be raised. An invalid option passed by the user will\n      cause SyntaxError to be raised.\n    \"\"\"\n\n# Example:\nopts = ArgParser( [('i', 'input-file', '_+_', '__file__'),\n                   ('o', 'output-file', '_+_', '__path__')] )\n\nfor opt, arg in opts( sys.argv[1:],\n                      ignore_delimiters = True,\n                      set_delimiter = None,\n                      clear = True )\n\n    \"\"\"\n        Options would be set as '-i', '--input-file' and '-o', '--output-file'. The '-i'\n      option will only accept an existing file as an argument and the '-o' option will\n      accept an argument that matches a path format, regardless of whether it exists. opts(...)\n      will return a key, item list from the provided user arguments. The '.items()' function\n      should not be used when calling the class with user arguments. If no arguments are given,\n      self (dict) is returned. The default is to clear self every time it is called unless 'clear'\n      is set to False.\n\n    \"\"\"\n\n```\n\n## Changelog\n\n- 0.3.5\n    - modified BBLogger and getLogger to hopefully maintain the right level across modules\n    - added 'rootlogger' level option\n\n- 0.3.8\n    - changed formatter to include filename instead of logger name\n\n- 0.3.9\n    - fixed logging error when not in terminal\n\n- 0.3.91\n    - fixed typo in basic logging.... maybe\n\n- 0.3.92\n    - 2nd attempt at fixing basic logging\n\n- 0.3.95\n    - yet another attempt to get basic log formatting right\n\n- 0.4.0\n    - re-wrote the basic log formatter\n\n## Thanks To:\n\n##### Colored logging with logging.Formatter()\n\n<p>Thread at <a href=\"https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output\">Stack Overflow</a> on python log formatting</p>\n\n<p>Adapted from <a href=\"https://stackoverflow.com/a/56944256/3638629\">original code</a> by user's at Stack Overflow</p>\n\n#### Thanks for providing help so people like me can learn :)\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "App utilities",
    "version": "0.4.0",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f5799e2db869298699b4be6540a18c7020f42f3c33d2328123eba6c8c61158c1",
                "md5": "066ebbcb9a9978f5b9116b0832dee5c5",
                "sha256": "e119fdc9255e3916a44a10e9e4d284ccbf6ab0c5c8ea0d43d352924dbfdf9044"
            },
            "downloads": -1,
            "filename": "bb_apputils-0.4.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "066ebbcb9a9978f5b9116b0832dee5c5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10,<4.0",
            "size": 17161,
            "upload_time": "2023-07-16T07:23:05",
            "upload_time_iso_8601": "2023-07-16T07:23:05.308406Z",
            "url": "https://files.pythonhosted.org/packages/f5/79/9e2db869298699b4be6540a18c7020f42f3c33d2328123eba6c8c61158c1/bb_apputils-0.4.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1f69072499274eaa09505e7aa5416d399441f9bc5f355d3a7c35a66f2b10fea3",
                "md5": "1d0d55127364a9bbecca831018a2a479",
                "sha256": "48084feb1f972d55200fac39689c5a4a8a4541b90b1c7c7bd60f533e7876e4eb"
            },
            "downloads": -1,
            "filename": "bb_apputils-0.4.0.tar.gz",
            "has_sig": false,
            "md5_digest": "1d0d55127364a9bbecca831018a2a479",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10,<4.0",
            "size": 17772,
            "upload_time": "2023-07-16T07:23:07",
            "upload_time_iso_8601": "2023-07-16T07:23:07.089241Z",
            "url": "https://files.pythonhosted.org/packages/1f/69/072499274eaa09505e7aa5416d399441f9bc5f355d3a7c35a66f2b10fea3/bb_apputils-0.4.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-07-16 07:23:07",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "bb-apputils"
}
        
Elapsed time: 0.09716s