Resolve merge conflicts
This commit is contained in:
commit
8a7e19db16
9 changed files with 138 additions and 95 deletions
|
|
@ -120,6 +120,14 @@ local:
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Documentation built (single version)."
|
@echo "Documentation built (single version)."
|
||||||
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
|
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
|
||||||
|
|
||||||
|
# build only that which updated since last run (no clean or index-creation)
|
||||||
|
localupdate:
|
||||||
|
make _check-env
|
||||||
|
make _html-build
|
||||||
|
@echo ""
|
||||||
|
@echo "Documentation built (single version, only updates, no auto-index)."
|
||||||
|
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
|
||||||
|
|
||||||
# note that this should be done for each relevant multiversion branch.
|
# note that this should be done for each relevant multiversion branch.
|
||||||
mv-index:
|
mv-index:
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ than, the doc-strings of each component in the [API](../Evennia-API).
|
||||||
- [MonitorHandler](./MonitorHandler)
|
- [MonitorHandler](./MonitorHandler)
|
||||||
- [TickerHandler](./TickerHandler)
|
- [TickerHandler](./TickerHandler)
|
||||||
- [Lock system](./Locks)
|
- [Lock system](./Locks)
|
||||||
- [FuncParser](FuncParser)
|
- [FuncParser](./FuncParser)
|
||||||
|
|
||||||
## Server and network
|
## Server and network
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -481,7 +481,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
|
||||||
return string, mapping
|
return string, mapping
|
||||||
|
|
||||||
|
|
||||||
def send_emote(sender, receivers, emote, anonymous_add="first"):
|
def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
||||||
"""
|
"""
|
||||||
Main access function for distribute an emote.
|
Main access function for distribute an emote.
|
||||||
|
|
||||||
|
|
@ -509,7 +509,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
|
||||||
# we escape the object mappings since we'll do the language ones first
|
# we escape the object mappings since we'll do the language ones first
|
||||||
# (the text could have nested object mappings).
|
# (the text could have nested object mappings).
|
||||||
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
||||||
|
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs
|
||||||
|
if 'anonymous_add' in kwargs:
|
||||||
|
anonymous_add = kwargs.pop('anonymous_add')
|
||||||
if anonymous_add and not "#%i" % sender.id in obj_mapping:
|
if anonymous_add and not "#%i" % sender.id in obj_mapping:
|
||||||
# no self-reference in the emote - add to the end
|
# no self-reference in the emote - add to the end
|
||||||
key = "#%i" % sender.id
|
key = "#%i" % sender.id
|
||||||
|
|
@ -567,7 +569,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
|
||||||
receiver_sdesc_mapping[rkey] = process_sdesc(receiver.key, receiver)
|
receiver_sdesc_mapping[rkey] = process_sdesc(receiver.key, receiver)
|
||||||
|
|
||||||
# do the template replacement of the sdesc/recog {#num} markers
|
# do the template replacement of the sdesc/recog {#num} markers
|
||||||
receiver.msg(sendemote.format(**receiver_sdesc_mapping))
|
receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -14,31 +14,31 @@ a server reload/reboot).
|
||||||
|
|
||||||
## Adding Traits to a typeclass
|
## Adding Traits to a typeclass
|
||||||
|
|
||||||
To access and manipulate traits on an object, its Typeclass needs to have a
|
To access and manipulate traits on an entity, its Typeclass needs to have a
|
||||||
`TraitHandler` assigned it. Usually, the handler is made available as `.traits`
|
`TraitHandler` assigned it. Usually, the handler is made available as `.traits`
|
||||||
(in the same way as `.tags` or `.attributes`).
|
(in the same way as `.tags` or `.attributes`). It's recommended to do this
|
||||||
|
using Evennia's `lazy_property` (which basically just means it's not
|
||||||
|
initialized until it's actually accessed).
|
||||||
|
|
||||||
Here's an example for adding the TraitHandler to the base Object class:
|
Here's an example for adding the TraitHandler to the base Object class:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# mygame/typeclasses/objects.py
|
# mygame/typeclasses/objects.py
|
||||||
|
|
||||||
from evennia import DefaultObject
|
from evennia import DefaultObject
|
||||||
from evennia.utils import lazy_property
|
from evennia.utils import lazy_property
|
||||||
from evennia.contrib.traits import TraitHandler
|
from evennia.contrib.traits import TraitHandler
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
class Object(DefaultObject):
|
class Object(DefaultObject):
|
||||||
...
|
...
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def traits(self):
|
def traits(self):
|
||||||
# this adds the handler as .traits
|
# this adds the handler as .traits
|
||||||
return TraitHandler(self)
|
return TraitHandler(self)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
After a reload you can now try adding some example traits:
|
|
||||||
|
|
||||||
## Using traits
|
## Using traits
|
||||||
|
|
||||||
|
|
@ -48,6 +48,7 @@ in Evennia).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# this is an example using the "static" trait, described below
|
# this is an example using the "static" trait, described below
|
||||||
|
|
||||||
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="static", base=4)
|
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="static", base=4)
|
||||||
>>> obj.traits.hunting.value
|
>>> obj.traits.hunting.value
|
||||||
4
|
4
|
||||||
|
|
@ -130,17 +131,19 @@ that varies slowly or not at all, and which may be modified in-place.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Counter
|
### Counter
|
||||||
|
::
|
||||||
|
|
||||||
min/unset base base+mod max/unset
|
min/unset base base+mod max/unset
|
||||||
|--------------|--------|---------X--------X------------|
|
|--------------|--------|---------X--------X------------|
|
||||||
current value
|
current value
|
||||||
= current
|
= current
|
||||||
+ mod
|
+ mod
|
||||||
|
|
||||||
A counter describes a value that can move from a base. The `current` property
|
A counter describes a value that can move from a base. The `.current` property
|
||||||
is the thing usually modified. It starts at the `base`. One can also add a modifier,
|
is the thing usually modified. It starts at the `.base`. One can also add a
|
||||||
which will both be added to the base and to current (forming .value).
|
modifier, which will both be added to the base and to current (forming
|
||||||
The min/max of the range are optional, a boundary set to None will remove it.
|
`.value`). The min/max of the range are optional, a boundary set to None will
|
||||||
|
remove it. A suggested use for a Counter Trait would be to track skill values.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="counter",
|
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="counter",
|
||||||
|
|
@ -160,10 +163,15 @@ The min/max of the range are optional, a boundary set to None will remove it.
|
||||||
|
|
||||||
Counters have some extra properties:
|
Counters have some extra properties:
|
||||||
|
|
||||||
`descs` is a dict {upper_bound:text_description}. This allows for easily
|
#### .descs
|
||||||
|
|
||||||
|
The `descs` property is a dict {upper_bound:text_description}. This allows for easily
|
||||||
storing a more human-friendly description of the current value in the
|
storing a more human-friendly description of the current value in the
|
||||||
interval. Here is an example for skill values between 0 and 10:
|
interval. Here is an example for skill values between 0 and 10:
|
||||||
|
::
|
||||||
|
|
||||||
{0: "unskilled", 1: "neophyte", 5: "trained", 7: "expert", 9: "master"}
|
{0: "unskilled", 1: "neophyte", 5: "trained", 7: "expert", 9: "master"}
|
||||||
|
|
||||||
The keys must be supplied from smallest to largest. Any values below the lowest and above the
|
The keys must be supplied from smallest to largest. Any values below the lowest and above the
|
||||||
highest description will be considered to be included in the closest description slot.
|
highest description will be considered to be included in the closest description slot.
|
||||||
By calling `.desc()` on the Counter, will you get the text matching the current `value`
|
By calling `.desc()` on the Counter, will you get the text matching the current `value`
|
||||||
|
|
@ -190,11 +198,11 @@ value.
|
||||||
The `rate` property defaults to 0. If set to a value different from 0, it
|
The `rate` property defaults to 0. If set to a value different from 0, it
|
||||||
allows the trait to change value dynamically. This could be used for example
|
allows the trait to change value dynamically. This could be used for example
|
||||||
for an attribute that was temporarily lowered but will gradually (or abruptly)
|
for an attribute that was temporarily lowered but will gradually (or abruptly)
|
||||||
recover after a certain time. The rate is given as change of the `current`
|
recover after a certain time. The rate is given as change of the current
|
||||||
per-second, and the .value will still be restrained by min/max boundaries, if
|
`.value` per-second, and this will still be restrained by min/max boundaries,
|
||||||
those are set.
|
if those are set.
|
||||||
|
|
||||||
It is also possible to set a ".ratetarget", for the auto-change to stop at
|
It is also possible to set a `.ratetarget`, for the auto-change to stop at
|
||||||
(rather than at the min/max boundaries). This allows the value to return to
|
(rather than at the min/max boundaries). This allows the value to return to
|
||||||
a previous value.
|
a previous value.
|
||||||
|
|
||||||
|
|
@ -220,35 +228,41 @@ a previous value.
|
||||||
>>> obj.traits.hunting.rate = 0 # disable auto-change
|
>>> obj.traits.hunting.rate = 0 # disable auto-change
|
||||||
|
|
||||||
```
|
```
|
||||||
Note that if rate is a non-integer, the resulting .value (at least until it
|
Note that if `.rate` is a non-integer, the resulting `.value` (at least until it
|
||||||
reaches the boundary) will likely also come out a float. If you expect an
|
reaches a boundary or rate-target) will also come out a float (so you can get a
|
||||||
integer, you must run run int() on the result yourself.
|
very exact value at the current time). If you expect an integer, you must run
|
||||||
|
`int()` (or something like `round()`) on the result yourself.
|
||||||
|
|
||||||
#### .percentage()
|
#### .percent()
|
||||||
|
|
||||||
If both min and max are defined, the `.percentage()` method of the trait will
|
If both min and max are defined, the `.percent()` method of the trait will
|
||||||
return the value as a percentage.
|
return the value as a percentage.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> obj.traits.hunting.percentage()
|
>>> obj.traits.hunting.percent()
|
||||||
"71.0%"
|
"71.0%"
|
||||||
|
>>> obj.traits.hunting.percent(formatting=None)
|
||||||
|
71.0
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gauge
|
### Gauge
|
||||||
|
|
||||||
This emulates a [fuel-] gauge that empties from a base+mod value.
|
This emulates a [fuel-] gauge that empties from a base+mod value.
|
||||||
|
::
|
||||||
|
|
||||||
min/0 max=base+mod
|
min/0 max=base+mod
|
||||||
|-----------------------X---------------------------|
|
|-----------------------X---------------------------|
|
||||||
value
|
value
|
||||||
= current
|
= current
|
||||||
|
|
||||||
The 'current' value will start from a full gauge. The .max property is
|
The `.current` value will start from a full gauge. The .max property is
|
||||||
read-only and is set by .base + .mod. So contrary to a Counter, the modifier
|
read-only and is set by `.base` + `.mod`. So contrary to a `Counter`, the
|
||||||
only applies to the max value of the gauge and not the current value. The
|
`.mod` modifier only applies to the max value of the gauge and not the current
|
||||||
minimum bound defaults to 0. This trait is useful for showing resources that
|
value. The minimum bound defaults to 0 if not set explicitly.
|
||||||
can deplete, like health, stamina and the like.
|
|
||||||
|
This trait is useful for showing commonly depletable resources like health,
|
||||||
|
stamina and the like.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> obj.traits.add("hp", "Health", trait_type="gauge", base=100)
|
>>> obj.traits.add("hp", "Health", trait_type="gauge", base=100)
|
||||||
|
|
@ -263,20 +277,24 @@ can deplete, like health, stamina and the like.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Same as Counters, Gauges can also have `descs` to describe the interval and can also
|
The Gauge trait is subclass of the Counter, so you have access to the same
|
||||||
have `rate` and `ratetarget` to auto-update the value. The rate is particularly useful
|
methods and properties where they make sense. So gauges can also have a
|
||||||
for gauges, for everything from poison slowly draining your health, to resting gradually
|
`.descs` dict to describe the intervals in text, and can use `.percent()` to
|
||||||
increasing it. You can also use the `.percentage()` function to show the current value
|
get how filled it is as a percentage etc.
|
||||||
as a percentage.
|
|
||||||
|
The `.rate` is particularly relevant for gauges - useful for everything
|
||||||
|
from poison slowly draining your health, to resting gradually increasing it.
|
||||||
|
|
||||||
### Trait
|
### Trait
|
||||||
|
|
||||||
A single value of any type.
|
A single value of any type.
|
||||||
|
|
||||||
This is the 'base' Trait, meant to inherit from if you want to make your own
|
This is the 'base' Trait, meant to inherit from if you want to invent
|
||||||
trait-types (see below). Its .value can be anything (that can be stored in an Attribute)
|
trait-types from scratch (most of the time you'll probably inherit from some of
|
||||||
and if it's a integer/float you can do arithmetic with it, but otherwise it
|
the more advanced trait-type classes though). A `Trait`s `.value` can be
|
||||||
acts just like a glorified Attribute.
|
anything (that can be stored in an Attribute) and if it's a integer/float you
|
||||||
|
can do arithmetic with it, but otherwise it acts just like a glorified
|
||||||
|
Attribute.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -291,38 +309,45 @@ acts just like a glorified Attribute.
|
||||||
|
|
||||||
## Expanding with your own Traits
|
## Expanding with your own Traits
|
||||||
|
|
||||||
A Trait is a class inhering from `evennia.contrib.traits.Trait` (or
|
A Trait is a class inhering from `evennia.contrib.traits.Trait` (or from one of
|
||||||
from one of the existing Trait classes).
|
the existing Trait classes).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# in a file, say, 'mygame/world/traits.py'
|
# in a file, say, 'mygame/world/traits.py'
|
||||||
|
|
||||||
from evennia.contrib.traits import Trait
|
from evennia.contrib.traits import StaticTrait
|
||||||
|
|
||||||
class RageTrait(Trait):
|
class RageTrait(StaticTrait):
|
||||||
|
|
||||||
trait_type = "rage"
|
trait_type = "rage"
|
||||||
default_keys = {
|
default_keys = {
|
||||||
"rage": 0
|
"rage": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def berserk(self):
|
||||||
|
self.mod = 100
|
||||||
|
|
||||||
|
def sedate(self):
|
||||||
|
self.mod = 0
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Above is an example custom-trait-class "rage" that stores a property "rage" on
|
Above is an example custom-trait-class "rage" that stores a property "rage" on
|
||||||
itself, with a default value of 0. This has all the
|
itself, with a default value of 0. This has all the functionality of a Trait -
|
||||||
functionality of a Trait - for example, if you do del on the `rage` property, it will be
|
for example, if you do del on the `rage` property, it will be set back to its
|
||||||
set back to its default (0). If you wanted to customize what it does, you
|
default (0). Above we also added some helper methods.
|
||||||
just add `rage` property get/setters/deleters on the class.
|
|
||||||
|
|
||||||
To add your custom RageTrait to Evennia, add the following to your settings file
|
To add your custom RageTrait to Evennia, add the following to your settings file
|
||||||
(assuming your class is in mygame/world/traits.py):
|
(assuming your class is in mygame/world/traits.py):
|
||||||
|
::
|
||||||
|
|
||||||
TRAIT_CLASS_PATHS = ["world.traits.RageTrait"]
|
TRAIT_CLASS_PATHS = ["world.traits.RageTrait"]
|
||||||
|
|
||||||
Reload the server and you should now be able to use your trait:
|
Reload the server and you should now be able to use your trait:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> obj.traits.add("mood", "A dark mood", rage=30)
|
>>> obj.traits.add("mood", "A dark mood", rage=30, trait_type='rage')
|
||||||
>>> obj.traits.mood.rage
|
>>> obj.traits.mood.rage
|
||||||
30
|
30
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -383,10 +383,12 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
else:
|
else:
|
||||||
mod_matches = _MODULE_PROTOTYPES
|
mod_matches = _MODULE_PROTOTYPES
|
||||||
|
|
||||||
|
allow_fuzzy = True
|
||||||
if key:
|
if key:
|
||||||
if key in mod_matches:
|
if key in mod_matches:
|
||||||
# exact match
|
# exact match
|
||||||
module_prototypes = [mod_matches[key]]
|
module_prototypes = [mod_matches[key]]
|
||||||
|
allow_fuzzy = False
|
||||||
else:
|
else:
|
||||||
# fuzzy matching
|
# fuzzy matching
|
||||||
module_prototypes = [
|
module_prototypes = [
|
||||||
|
|
@ -410,7 +412,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
if key:
|
if key:
|
||||||
# exact or partial match on key
|
# exact or partial match on key
|
||||||
exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key")
|
exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key")
|
||||||
if not exact_match:
|
if not exact_match and allow_fuzzy:
|
||||||
# try with partial match instead
|
# try with partial match instead
|
||||||
db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key")
|
db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key")
|
||||||
else:
|
else:
|
||||||
|
|
@ -427,7 +429,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
nmodules = len(module_prototypes)
|
nmodules = len(module_prototypes)
|
||||||
ndbprots = db_matches.count()
|
ndbprots = db_matches.count()
|
||||||
if nmodules + ndbprots != 1:
|
if nmodules + ndbprots != 1:
|
||||||
raise KeyError(f"Found {nmodules + ndbprots} matching prototypes.")
|
raise KeyError(f"Found {nmodules + ndbprots} matching prototypes {module_prototypes}.")
|
||||||
|
|
||||||
if return_iterators:
|
if return_iterators:
|
||||||
# trying to get the entire set of prototypes - we must paginate
|
# trying to get the entire set of prototypes - we must paginate
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ def _portal_maintenance():
|
||||||
|
|
||||||
_MAINTENANCE_COUNT += 1
|
_MAINTENANCE_COUNT += 1
|
||||||
|
|
||||||
if _MAINTENANCE_COUNT % (3600 * 7) == 0:
|
if _MAINTENANCE_COUNT % (60 * 7) == 0:
|
||||||
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
||||||
# (see https://github.com/evennia/evennia/issues/1376)
|
# (see https://github.com/evennia/evennia/issues/1376)
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
|
||||||
|
|
@ -140,13 +140,16 @@ def _server_maintenance():
|
||||||
_GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now
|
_GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now
|
||||||
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME)
|
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME)
|
||||||
|
|
||||||
if _MAINTENANCE_COUNT % 300 == 0:
|
if _MAINTENANCE_COUNT % 5 == 0:
|
||||||
# check cache size every 5 minutes
|
# check cache size every 5 minutes
|
||||||
_FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE)
|
_FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE)
|
||||||
if _MAINTENANCE_COUNT % 3700 == 0:
|
if _MAINTENANCE_COUNT % 60 == 0:
|
||||||
|
# validate scripts every hour
|
||||||
|
evennia.ScriptDB.objects.validate()
|
||||||
|
if _MAINTENANCE_COUNT % 61 == 0:
|
||||||
# validate channels off-sync with scripts
|
# validate channels off-sync with scripts
|
||||||
evennia.CHANNEL_HANDLER.update()
|
evennia.CHANNEL_HANDLER.update()
|
||||||
if _MAINTENANCE_COUNT % (3600 * 7) == 0:
|
if _MAINTENANCE_COUNT % (60 * 7) == 0:
|
||||||
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
||||||
# (see https://github.com/evennia/evennia/issues/1376)
|
# (see https://github.com/evennia/evennia/issues/1376)
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class TestServer(TestCase):
|
||||||
_FLUSH_CACHE=DEFAULT,
|
_FLUSH_CACHE=DEFAULT,
|
||||||
connection=DEFAULT,
|
connection=DEFAULT,
|
||||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||||
_MAINTENANCE_COUNT=600 - 1,
|
_MAINTENANCE_COUNT=5 - 1,
|
||||||
ServerConfig=DEFAULT,
|
ServerConfig=DEFAULT,
|
||||||
) as mocks:
|
) as mocks:
|
||||||
mocks["connection"].close = MagicMock()
|
mocks["connection"].close = MagicMock()
|
||||||
|
|
@ -65,7 +65,7 @@ class TestServer(TestCase):
|
||||||
_FLUSH_CACHE=DEFAULT,
|
_FLUSH_CACHE=DEFAULT,
|
||||||
connection=DEFAULT,
|
connection=DEFAULT,
|
||||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||||
_MAINTENANCE_COUNT=3600 - 1,
|
_MAINTENANCE_COUNT=60 - 1,
|
||||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||||
ServerConfig=DEFAULT,
|
ServerConfig=DEFAULT,
|
||||||
) as mocks:
|
) as mocks:
|
||||||
|
|
@ -82,7 +82,7 @@ class TestServer(TestCase):
|
||||||
_FLUSH_CACHE=DEFAULT,
|
_FLUSH_CACHE=DEFAULT,
|
||||||
connection=DEFAULT,
|
connection=DEFAULT,
|
||||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||||
_MAINTENANCE_COUNT=3700 - 1,
|
_MAINTENANCE_COUNT=61 - 1,
|
||||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||||
ServerConfig=DEFAULT,
|
ServerConfig=DEFAULT,
|
||||||
) as mocks:
|
) as mocks:
|
||||||
|
|
@ -100,7 +100,7 @@ class TestServer(TestCase):
|
||||||
_FLUSH_CACHE=DEFAULT,
|
_FLUSH_CACHE=DEFAULT,
|
||||||
connection=DEFAULT,
|
connection=DEFAULT,
|
||||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
_MAINTENANCE_COUNT=(60 * 7) - 1,
|
||||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||||
ServerConfig=DEFAULT,
|
ServerConfig=DEFAULT,
|
||||||
) as mocks:
|
) as mocks:
|
||||||
|
|
|
||||||
|
|
@ -1711,36 +1711,39 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prompt (str): The yes/no question to ask. This takes an optional formatting
|
prompt (str): The yes/no question to ask. This takes an optional formatting
|
||||||
marker `{yesno}` which will be filled with 'Y/N', [Y]/N or Y/[N]
|
marker `{options}` which will be filled with 'Y/N', '[Y]/N' or
|
||||||
depending on the setting of `default`. If `allow_abort`, then the
|
'Y/[N]' depending on the setting of `default`. If `allow_abort` is set,
|
||||||
`A(bort)` will also be available.
|
then the 'A(bort)' option will also be available.
|
||||||
yes_action (callable or str): If a callable, this will be called
|
yes_action (callable or str): If a callable, this will be called
|
||||||
with `(caller, *args, **kwargs) when the yes-choice is made.
|
with `(caller, *args, **kwargs)` when the Yes-choice is made.
|
||||||
If a string, this string will be echoed back to the caller.
|
If a string, this string will be echoed back to the caller.
|
||||||
no_action (callable or str): If a callable, this will be called
|
no_action (callable or str): If a callable, this will be called
|
||||||
with `(caller, *args, **kwargs)` when the no-choice is made.
|
with `(caller, *args, **kwargs)` when the No-choice is made.
|
||||||
If a string, this string will be echoed back to the caller.
|
If a string, this string will be echoed back to the caller.
|
||||||
default (str optional): One of "N", "Y", "A" or None for no default.
|
default (str optional): This is what the user will get if they just press the
|
||||||
If "A" is given, `allow_abort` is assumed set. The user can choose
|
return key without giving any input. One of 'N', 'Y', 'A' or 'None'
|
||||||
the default option just by pressing return.
|
for no default. If 'A' is given, `allow_abort` is auto-set.
|
||||||
allow_abort (bool, optional): If set, the Q(uit) option is available,
|
allow_abort (bool, optional): If set, the 'A(bort)' option is available
|
||||||
which is neither yes or no.
|
(a third option meaning neither yes or no but just exits the prompt).
|
||||||
session (Session, optional): This allows to specify the
|
session (Session, optional): This allows to specify the
|
||||||
session to send the prompt to. It's usually only needed if `caller`
|
session to send the prompt to. It's usually only needed if `caller`
|
||||||
is an Account in multisession modes greater than 2. The session is
|
is an Account in multisession modes greater than 2. The session is
|
||||||
then updated by the command and is available (for example in
|
then updated by the command and is available (for example in
|
||||||
callbacks) through `caller.ndb._yes_no_question.session`.
|
callbacks) through `caller.ndb._yes_no_question.session`.
|
||||||
*args, **kwargs: These are passed into the callables, if any.
|
*args, **kwargs: These are passed into the callables.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If default and allow_abort clashes.
|
RuntimeError, FooError: If default and allow_abort clashes.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
::
|
||||||
|
|
||||||
ask_yes_no(caller, "Are you happy {yesno}?",
|
# just returning strings
|
||||||
"you answered yes", "you answered no")
|
ask_yes_no(caller, "Are you happy {options}?",
|
||||||
ask_yes_no(caller, "Are you sad {yesno}?",
|
"you answered yes", "you answered no")
|
||||||
_callable_yes, _callable_no, allow_abort=True)
|
# trigger callables
|
||||||
|
ask_yes_no(caller, "Are you sad {options}?",
|
||||||
|
_callable_yes, _callable_no, allow_abort=True)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _callable_yes_txt(caller, *args, **kwargs):
|
def _callable_yes_txt(caller, *args, **kwargs):
|
||||||
|
|
@ -1761,20 +1764,20 @@ def ask_yes_no(caller, prompt, yes_action, no_action, default=None,
|
||||||
kwargs['no_txt'] = str(no_action)
|
kwargs['no_txt'] = str(no_action)
|
||||||
no_action = _callable_no_txt
|
no_action = _callable_no_txt
|
||||||
|
|
||||||
# prepare the prompt with yesno suffix
|
# prepare the prompt with options
|
||||||
suffix = "Y/N"
|
options = "Y/N"
|
||||||
abort_txt = "/Abort" if allow_abort else ""
|
abort_txt = "/Abort" if allow_abort else ""
|
||||||
if default:
|
if default:
|
||||||
default = default.lower()
|
default = default.lower()
|
||||||
if default == "y":
|
if default == "y":
|
||||||
suffix = "[Y]/N"
|
options = "[Y]/N"
|
||||||
elif default == "n":
|
elif default == "n":
|
||||||
suffix = "Y/[N]"
|
options = "Y/[N]"
|
||||||
elif default == "a":
|
elif default == "a":
|
||||||
allow_abort = True
|
allow_abort = True
|
||||||
abort_txt = "/[A]bort"
|
abort_txt = "/[A]bort"
|
||||||
suffix += abort_txt
|
options += abort_txt
|
||||||
prompt = prompt.format(yesno=suffix)
|
prompt = prompt.format(options=options)
|
||||||
|
|
||||||
caller.ndb._yes_no_question = _Prompt()
|
caller.ndb._yes_no_question = _Prompt()
|
||||||
caller.ndb._yes_no_question.session = session
|
caller.ndb._yes_no_question.session = session
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue