Module Gnumed.business.gmGender

GNUmed gender handling.

Expand source code
# -*- coding: utf-8 -*-

"""GNUmed gender handling."""
#============================================================
__author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
__license__ = "GPL"

# std lib
import sys
import logging

# GNUmed
if __name__ == '__main__':
        sys.path.insert(0, '../../')
from Gnumed.pycommon import gmLog2
from Gnumed.pycommon import gmI18N
if __name__ == '__main__':
        _ = lambda x:x
        gmI18N.activate_locale()
        gmI18N.install_domain()
from Gnumed.pycommon import gmBusinessDBObject
from Gnumed.pycommon import gmPG2


_log = logging.getLogger('gm.person')

__LIST__gender_defs = None

__MAP__gender2salutation = None
__MAP__gender2string = None
__MAP__gender2symbol = None

#============================================================
_SQL_get_gender_defs = 'SELECT * FROM dem.v_gender_defs'

class cGenderDef(gmBusinessDBObject.cBusinessDBObject):
        """Represents a gender definition."""

        _cmd_fetch_payload = '%s WHERE pk_gender_label = %%s' % _SQL_get_gender_defs
        _cmds_store_payload = [
                """UPDATE dem.gender_label SET
                                tag = gm.nullify_empty_string(%(tag)s),
                                label = gm.nullify_empty_string(%(name)s),
                                symbol = gm.nullify_empty_string(%(symbol)s),
                                comment = gm.nullify_empty_string(%(comment)s)
                        WHERE
                                pk = %(pk_gender_label)s
                                        AND
                                xmin = %(xmin_gender_label)s
                        RETURNING
                                xmin AS xmin_gender_label,
                                _(tag) as l10n_tag,
                                _(label) as l10n_name,
                                _(symbol) as l10n_symbol
                """
        ]
        _updatable_fields = [
                'tag',
                'name',
                'symbol',
                'comment'
        ]
        #--------------------------------------------------------
        def format(self):
                symbol = map_gender2symbol(gender = self._payload['tag'])
                line = '%(l10n_tag)s: %(l10n_name)s' % self._payload
                if symbol:
                        line += ' (%s)' % symbol
                return line

        #--------------------------------------------------------
        def store_field_translation(self, original:str, translation:str, link_obj=None):
                gmPG2.update_translation_in_database (
                        language = None,        # use current DB language
                        original = original,
                        translation = translation,
                        link_obj = link_obj
                )
                invalidate_genders_cache()
                self.refetch_payload(link_obj = link_obj)

        #--------------------------------------------------------
        def save_payload(self, conn:gmPG2.dbapi.extras.DictConnection=None) -> tuple[bool, tuple]:
                result = super().save_payload(conn = conn)
                invalidate_genders_cache()
                return result

        #--------------------------------------------------------
        def _get_is_in_use(self):
                SQL = 'SELECT EXISTS (SELECT 1 FROM dem.identity WHERE dem.identity.gender = %(tag)s)'
                args = {'tag': self._payload['tag']}
                rows = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}])
                return rows[0][0]

        is_in_use = property(_get_is_in_use)

#------------------------------------------------------------
def get_genders(as_instance:bool=False, order_by:str=None) -> list:
        """Retrieves the list of known genders from the database."""
        global __LIST__gender_defs
        if not __LIST__gender_defs:
                SQL = _SQL_get_gender_defs
                if order_by:
                        SQL += ' ORDER BY %s' % order_by
                __LIST__gender_defs = gmPG2.run_ro_queries(queries = [{'cmd': SQL}])
                _log.debug('gender definitions in database: %s' % __LIST__gender_defs)
        if not as_instance:
                return __LIST__gender_defs

        return [ cGenderDef(row = {'data': r, 'pk_field': 'pk_gender_label'}) for r in __LIST__gender_defs ]

#------------------------------------------------------------
def create_gender_def(tag:str=None, name:str=None, comment:str=None, symbol:str=None):
        args = {
                'tag': tag,
                'name': name,
                'cmt': comment,
                'sym': symbol
        }
        SQL = """
                INSERT INTO dem.gender_label (tag, label, comment, symbol) VALUES (
                        gm.nullify_empty_string(%(tag)s),
                        gm.nullify_empty_string(%(name)s),
                        gm.nullify_empty_string(%(cmt)s),
                        gm.nullify_empty_string(%(sym)s)
                )
                RETURNING pk
        """
        rows = gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}], return_data = True)
        invalidate_genders_cache()
        return cGenderDef(aPK_obj = rows[0]['pk'])

#------------------------------------------------------------
def delete_gender_def(pk_gender_label=None):
        args = {'pk': pk_gender_label}
        SQL = 'DELETE FROM dem.gender_label WHERE pk = %(pk)s'
        gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}])
        invalidate_genders_cache()
        return True

#------------------------------------------------------------
#------------------------------------------------------------
map_gender2mf = {
        'm': 'm',
        'f': 'f',
        'tf': 'f',
        'tm': 'm',
        'h': 'mf'
}

# https://tools.ietf.org/html/rfc6350#section-6.2.7
# M F O N U
map_gender2vcard = {
        'm': 'M',
        'f': 'F',
        'tf': 'F',
        'tm': 'M',
        'h': 'O',
        None: 'U'
}

#------------------------------------------------------------
def map_gender2tag(gender:str=None) -> str:
        for gdef in get_genders():
                if gender in [gdef['tag'], gdef['l10n_tag'], gdef['name'], gdef['l10n_name'], gdef['symbol'], gdef['l10n_symbol']]:
                        return gdef['tag']

        return None

#------------------------------------------------------------
def map_gender2string(gender:str=None) -> str:
        """Maps GNUmed related i18n-aware gender specifiers to a human-readable string."""
        genders = get_genders()
        global __MAP__gender2string
        if not __MAP__gender2string:
                __MAP__gender2string = {
                        'm': _('male'),
                        'f': _('female'),
                        'tf': '',
                        'tm': '',
                        'h': '',
                        None: _('unknown gender')
                }
                for g in genders:
                        if g['l10n_name']:
                                __MAP__gender2string[g['l10n_tag']] = g['l10n_name']
                                __MAP__gender2string[g['tag']] = g['l10n_name']
                _log.debug('gender -> string mapping: %s' % __MAP__gender2string)
        try:
                return __MAP__gender2string[gender]

        except KeyError:
                return '?%s?' % gender

#------------------------------------------------------------
def map_gender2symbol(gender:str=None) -> str:
        """Maps GNUmed related i18n-aware gender specifiers to a unicode symbol."""
        genders = get_genders()
        global __MAP__gender2symbol
        if not __MAP__gender2symbol:
                # built-in defaults
                __MAP__gender2symbol = {
                        'm': '\u2642',
                        'f': '\u2640',
                        'tf': '\u26A5\u2640',
                        #'tf': u'\u2642\u2640-\u2640',
                        'tm': '\u26A5\u2642',
                        #'tm': u'\u2642\u2640-\u2642',
                        'h': '\u26A5',
                        #'h': u'\u2642\u2640',
                        None: '?\u26A5?'
                }
                # update from database, possibly adding more genders
                for g in genders:
                        if g['l10n_symbol']:
                                __MAP__gender2symbol[g['l10n_tag']] = g['l10n_symbol']
                                __MAP__gender2symbol[g['tag']] = g['l10n_symbol']
                _log.debug('gender -> symbol mapping: %s' % __MAP__gender2symbol)
        try:
                return __MAP__gender2symbol[gender]

        except KeyError:
                return '?%s?' % gender

#------------------------------------------------------------
def map_gender2salutation(gender=None):
        """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation."""
        global __MAP__gender2salutation
        if not __MAP__gender2salutation:
                __MAP__gender2salutation = {
                        'm': _('Mr'),
                        'f': _('Mrs'),
                        'tf': '',
                        'tm': '',
                        'h': '',
                        None: ''
                }
                _log.debug('gender -> salutation mapping: %s' % __MAP__gender2salutation)
        try:
                return __MAP__gender2salutation[gender]

        except KeyError:
                return ''

#------------------------------------------------------------
def map_firstnames2gender(firstnames=None):
        """Try getting the gender for the given first name."""

        if firstnames is None:
                return None

        SQL = 'SELECT gender FROM dem.name_gender_map WHERE name ILIKE %(fn)s LIMIT 1'
        args = {'fn': firstnames}
        rows = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}])
        if not rows:
                return None

        return rows[0][0]

#------------------------------------------------------------
def invalidate_genders_cache():
        global __LIST__gender_defs
        __LIST__gender_defs = None

#============================================================
# main/testing
#============================================================
if __name__ == '__main__':

        if len(sys.argv) == 1:
                sys.exit()

        if sys.argv[1] != 'test':
                sys.exit()

        gmLog2.print_logfile_name()

        #--------------------------------------------------------
        def test_gender_list():
                genders = get_genders()
                print("\n\nRetrieving gender enum (tag, name, symbol):")
                for gender in genders:
                        print("%s, %s, %s, %s" % (gender['tag'], gender['l10n_name'], gender['l10n_symbol'], map_gender2symbol(gender['tag'])))

        #--------------------------------------------------------
        def test_get_gender_defs():
                for l in get_genders(as_instance = True):
                        print(l.format(), ':', l.is_in_use)

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

        gmPG2.request_login_params(setup_pool = True, force_tui = True)
        #map_gender2salutation('m')
        test_get_gender_defs()
        #test_gender_list()

Functions

def create_gender_def(tag: str = None, name: str = None, comment: str = None, symbol: str = None)
Expand source code
def create_gender_def(tag:str=None, name:str=None, comment:str=None, symbol:str=None):
        args = {
                'tag': tag,
                'name': name,
                'cmt': comment,
                'sym': symbol
        }
        SQL = """
                INSERT INTO dem.gender_label (tag, label, comment, symbol) VALUES (
                        gm.nullify_empty_string(%(tag)s),
                        gm.nullify_empty_string(%(name)s),
                        gm.nullify_empty_string(%(cmt)s),
                        gm.nullify_empty_string(%(sym)s)
                )
                RETURNING pk
        """
        rows = gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}], return_data = True)
        invalidate_genders_cache()
        return cGenderDef(aPK_obj = rows[0]['pk'])
def delete_gender_def(pk_gender_label=None)
Expand source code
def delete_gender_def(pk_gender_label=None):
        args = {'pk': pk_gender_label}
        SQL = 'DELETE FROM dem.gender_label WHERE pk = %(pk)s'
        gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}])
        invalidate_genders_cache()
        return True
def get_genders(as_instance: bool = False, order_by: str = None) ‑> list

Retrieves the list of known genders from the database.

Expand source code
def get_genders(as_instance:bool=False, order_by:str=None) -> list:
        """Retrieves the list of known genders from the database."""
        global __LIST__gender_defs
        if not __LIST__gender_defs:
                SQL = _SQL_get_gender_defs
                if order_by:
                        SQL += ' ORDER BY %s' % order_by
                __LIST__gender_defs = gmPG2.run_ro_queries(queries = [{'cmd': SQL}])
                _log.debug('gender definitions in database: %s' % __LIST__gender_defs)
        if not as_instance:
                return __LIST__gender_defs

        return [ cGenderDef(row = {'data': r, 'pk_field': 'pk_gender_label'}) for r in __LIST__gender_defs ]
def invalidate_genders_cache()
Expand source code
def invalidate_genders_cache():
        global __LIST__gender_defs
        __LIST__gender_defs = None
def map_firstnames2gender(firstnames=None)

Try getting the gender for the given first name.

Expand source code
def map_firstnames2gender(firstnames=None):
        """Try getting the gender for the given first name."""

        if firstnames is None:
                return None

        SQL = 'SELECT gender FROM dem.name_gender_map WHERE name ILIKE %(fn)s LIMIT 1'
        args = {'fn': firstnames}
        rows = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}])
        if not rows:
                return None

        return rows[0][0]
def map_gender2salutation(gender=None)

Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.

Expand source code
def map_gender2salutation(gender=None):
        """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation."""
        global __MAP__gender2salutation
        if not __MAP__gender2salutation:
                __MAP__gender2salutation = {
                        'm': _('Mr'),
                        'f': _('Mrs'),
                        'tf': '',
                        'tm': '',
                        'h': '',
                        None: ''
                }
                _log.debug('gender -> salutation mapping: %s' % __MAP__gender2salutation)
        try:
                return __MAP__gender2salutation[gender]

        except KeyError:
                return ''
def map_gender2string(gender: str = None) ‑> str

Maps GNUmed related i18n-aware gender specifiers to a human-readable string.

Expand source code
def map_gender2string(gender:str=None) -> str:
        """Maps GNUmed related i18n-aware gender specifiers to a human-readable string."""
        genders = get_genders()
        global __MAP__gender2string
        if not __MAP__gender2string:
                __MAP__gender2string = {
                        'm': _('male'),
                        'f': _('female'),
                        'tf': '',
                        'tm': '',
                        'h': '',
                        None: _('unknown gender')
                }
                for g in genders:
                        if g['l10n_name']:
                                __MAP__gender2string[g['l10n_tag']] = g['l10n_name']
                                __MAP__gender2string[g['tag']] = g['l10n_name']
                _log.debug('gender -> string mapping: %s' % __MAP__gender2string)
        try:
                return __MAP__gender2string[gender]

        except KeyError:
                return '?%s?' % gender
def map_gender2symbol(gender: str = None) ‑> str

Maps GNUmed related i18n-aware gender specifiers to a unicode symbol.

Expand source code
def map_gender2symbol(gender:str=None) -> str:
        """Maps GNUmed related i18n-aware gender specifiers to a unicode symbol."""
        genders = get_genders()
        global __MAP__gender2symbol
        if not __MAP__gender2symbol:
                # built-in defaults
                __MAP__gender2symbol = {
                        'm': '\u2642',
                        'f': '\u2640',
                        'tf': '\u26A5\u2640',
                        #'tf': u'\u2642\u2640-\u2640',
                        'tm': '\u26A5\u2642',
                        #'tm': u'\u2642\u2640-\u2642',
                        'h': '\u26A5',
                        #'h': u'\u2642\u2640',
                        None: '?\u26A5?'
                }
                # update from database, possibly adding more genders
                for g in genders:
                        if g['l10n_symbol']:
                                __MAP__gender2symbol[g['l10n_tag']] = g['l10n_symbol']
                                __MAP__gender2symbol[g['tag']] = g['l10n_symbol']
                _log.debug('gender -> symbol mapping: %s' % __MAP__gender2symbol)
        try:
                return __MAP__gender2symbol[gender]

        except KeyError:
                return '?%s?' % gender
def map_gender2tag(gender: str = None) ‑> str
Expand source code
def map_gender2tag(gender:str=None) -> str:
        for gdef in get_genders():
                if gender in [gdef['tag'], gdef['l10n_tag'], gdef['name'], gdef['l10n_name'], gdef['symbol'], gdef['l10n_symbol']]:
                        return gdef['tag']

        return None

Classes

class cGenderDef (aPK_obj: int | dict = None, row: dict = None, link_obj=None)

Represents a gender definition.

Call init from child classes like so:

    super().__init__(aPK_obj = aPK_obj, row = row, link_obj = link_obj)

Args

aPK_obj
retrieve data from backend
  • an scalar value the ._cmd_fetch_payload WHERE condition must be a simple column: "… WHERE pk_col = %s"
  • a dictionary of values the ._cmd_fetch_payload WHERE condition must consume the dictionary and produce a unique row
row
must hold the fields
  • data: list of column values for the row selected by ._cmd_fetch_payload (as returned by cursor.fetchone() in the DB-API)
  • pk_field: the name of the primary key column OR
  • pk_obj: a dictionary suitable for being passed to cursor.execute and holding the primary key values, used for composite PKs
  • for example:
    row = {
            'data': rows[0],
            'pk_field': 'pk_XXX (the PK column name)',
            'pk_obj': {'pk_col1': pk_col1_val, 'pk_col2': pk_col2_val}
    }
    
    rows = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    objects = [ cChildClass(row = {'data': r, 'pk_field': 'the PK column name'}) for r in rows ]
    
Expand source code
class cGenderDef(gmBusinessDBObject.cBusinessDBObject):
        """Represents a gender definition."""

        _cmd_fetch_payload = '%s WHERE pk_gender_label = %%s' % _SQL_get_gender_defs
        _cmds_store_payload = [
                """UPDATE dem.gender_label SET
                                tag = gm.nullify_empty_string(%(tag)s),
                                label = gm.nullify_empty_string(%(name)s),
                                symbol = gm.nullify_empty_string(%(symbol)s),
                                comment = gm.nullify_empty_string(%(comment)s)
                        WHERE
                                pk = %(pk_gender_label)s
                                        AND
                                xmin = %(xmin_gender_label)s
                        RETURNING
                                xmin AS xmin_gender_label,
                                _(tag) as l10n_tag,
                                _(label) as l10n_name,
                                _(symbol) as l10n_symbol
                """
        ]
        _updatable_fields = [
                'tag',
                'name',
                'symbol',
                'comment'
        ]
        #--------------------------------------------------------
        def format(self):
                symbol = map_gender2symbol(gender = self._payload['tag'])
                line = '%(l10n_tag)s: %(l10n_name)s' % self._payload
                if symbol:
                        line += ' (%s)' % symbol
                return line

        #--------------------------------------------------------
        def store_field_translation(self, original:str, translation:str, link_obj=None):
                gmPG2.update_translation_in_database (
                        language = None,        # use current DB language
                        original = original,
                        translation = translation,
                        link_obj = link_obj
                )
                invalidate_genders_cache()
                self.refetch_payload(link_obj = link_obj)

        #--------------------------------------------------------
        def save_payload(self, conn:gmPG2.dbapi.extras.DictConnection=None) -> tuple[bool, tuple]:
                result = super().save_payload(conn = conn)
                invalidate_genders_cache()
                return result

        #--------------------------------------------------------
        def _get_is_in_use(self):
                SQL = 'SELECT EXISTS (SELECT 1 FROM dem.identity WHERE dem.identity.gender = %(tag)s)'
                args = {'tag': self._payload['tag']}
                rows = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}])
                return rows[0][0]

        is_in_use = property(_get_is_in_use)

Ancestors

Instance variables

var is_in_use
Expand source code
def _get_is_in_use(self):
        SQL = 'SELECT EXISTS (SELECT 1 FROM dem.identity WHERE dem.identity.gender = %(tag)s)'
        args = {'tag': self._payload['tag']}
        rows = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}])
        return rows[0][0]

Methods

def store_field_translation(self, original: str, translation: str, link_obj=None)
Expand source code
def store_field_translation(self, original:str, translation:str, link_obj=None):
        gmPG2.update_translation_in_database (
                language = None,        # use current DB language
                original = original,
                translation = translation,
                link_obj = link_obj
        )
        invalidate_genders_cache()
        self.refetch_payload(link_obj = link_obj)

Inherited members