-
Notifications
You must be signed in to change notification settings - Fork 28
Implement fluent.bundle namespace package #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement fluent.bundle namespace package #81
Conversation
and other bug fixes
…mbers Thanks Babel!
(to at least avoid a blank page on PyPI)
(missing runtests.py)
@stasm We have a problem on this - I've checked and PyPI considers |
I didn't realize PyPI would treat these names as equal. @Pike found https://www.python.org/dev/peps/pep-0503/#normalized-names which mentions (and mandates) this behavior. I think |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of the fluent.bundle
to fluent.runtime
rename, GitHub shows the comments I saved before as obsolete. I'm submitting this partial review to not lose them. I should be able to finish the review today.
README.md
Outdated
Date and time | ||
------------- | ||
|
||
Python `dateime.datetime` and `datetime.date` objects are also passed through |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: datetime.datetime
.
|
||
def has_message(self, message_id): | ||
try: | ||
self._get_message(message_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using _get_message
here means that it becomes possible to check whether a message has a given attribute. I don't think we should allow this. has_message
should be used to only check of a message of the given id exists in the bundle.
In the future, we might want to add a new API for checking if a message of a given shape (i.e. with given attribute names) exists. For now, I'd suggest making has_message
a bit more dumb :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, will do so!
foo = Foo { $num } | ||
bar = { foo } | ||
baz | ||
.attr = Baz Attribute { $num } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the =
after the identifier. It's required by the spec since Syntax 0.6. python-fluent
supports the old syntax, too, for obscure compatibility reasons; there are parser tests to verify it. The AST for both syntaxes is the same.
fluent-syntax/runtests.py
Outdated
@@ -0,0 +1 @@ | |||
../fluent-bundle/runtests.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update the symlink to the new directory names.
fluent.bundle/fluent/bundle/types.py
Outdated
FORMAT_STYLE_DECIMAL = "decimal" | ||
FORMAT_STYLE_CURRENCY = "currency" | ||
FORMAT_STYLE_PERCENT = "percent" | ||
FORMAT_STYLES = set([FORMAT_STYLE_DECIMAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: for consistency with CURRENCY_DISPLAY_OPTIONS
, perhaps rename this to FORMAT_STYLE_OPTIONS
? Or use ..._SET
for both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go with _OPTIONS
because I also had DATE_STYLE_OPTIONS
and TIME_STYLE_OPTIONS
, and _SET
ties you to a set
when it's possible another container object might be more appropriate.
.gitignore
Outdated
@@ -2,3 +2,5 @@ | |||
*.pyc | |||
.eggs/ | |||
fluent.egg-info/ | |||
fluent_syntax.egg-info/ | |||
fluent_bundle.egg-info/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename this to fluent_runtime
?
README.md
Outdated
To generate translations using the ``fluent.runtime`` package, you start with | ||
the `FluentBundle` class: | ||
|
||
>>> from fluent.bundle import FluentBundle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more rename here: from fluent.runtime
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ones I was thinking of leaving i.e. currently it is just the package (and things that reflect the package name, like the directory) that are renamed to fluent.runtime
. Imports are still using fluent.bundle
, both in the docs and internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I didn't understand that, then. I see what you mean: fluent.runtime
the package can provide more than one module namespaced under fluent
.
I think I'd be a bit surprised if pip install fluent.runtime
didn't make import fluent.runtime
work. It might be the choice of the period as the separator in the package name—personally, I would find it easier to understand if that name mapped exactly to the name of the module which is installed.
I don't want to block on this; I'll leave this up to you.
README.md
Outdated
You can specify your own formatting options on the arguments passed in by | ||
wrapping your numeric arguments with `fluent.types.fluent_number`: | ||
|
||
>>> from fluent.bundle.types import fluent_number |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here: from fluent.runtime.types
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here. In fact, if we wanted, we could also promote some of these user-facing things into higher level - e.g. fluent.bundle.types
could go to fluent.types
, which might make importing fluent_number
and fluent_date
a bit handier. We'd just have to add fluent.types
to setup.py to enable this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't we also need to move fluent.runtime/fluent/bundle/types.py
to fluent.runtime/fluent/types/__init__.py
to make it a new module namespaced under fluent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right, we'd need that too. Your other comment convinced be that it is probably better to go with fluent.runtime
, it would be surprising to not have fluent.runtime
importable.
README.md
Outdated
|
||
To specify options from Python code, use `fluent.bundle.types.fluent_date`: | ||
|
||
>>> from fluent.bundle.types import fluent_date |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here as well: from fluent.runtime.types
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think I'm done with the review. It was a pleasure to read this code; it's very well commented and easy to follow. I'm super happy that Fluent is about to have a full implementation in Python!
I have a few minor suggestions inline which shouldn't block this PR. The high-level comment which starts in the next paragraph shouldn't block it either.
I've been thinking about how the resolver here differs from the one I wrote for fluent.js
. In the JS one, I tried to stick to the following rule: all handler functions always return instances of FluentType
subclasses. (Due to performance reasons, and thanks to the built-in toString
and valueOf
methods, native JS strings count as FluentTypes
as well.) All logic is performed on these instances. At the very end, they all end up in a Pattern
, where they get formatted to strings via toString
.
Here, it looks like you've taken a different approach. fully_resolve
is called recursively as many times as needed to get to a string. The consequence of this, IIUC, is that the resolver needs a few more handlers for types which are not AST nodes, e.g. for int
and for FluentNumber
. It also means that handle_variable_reference
can get away with returning native types, rather than always converting them to instances of corresponding FluentTypes
. This fact caught me off-guard when I was reading the code.
I'm also not entirely sure if I understand the need for FluentInt
, FluentFloat
, etc. Naively, I would assume that FluentNumber.value
can hold any number type. match
could have specialized branches for dealing with different pair of number values. What is the benefit of using types which subclass int
, float
and Decimal
? Is it so that it's easier to write custom Fluent functions? I would be OK requiring that custom function explicitly use FluentType.value
.
In fact, I would even suggest adding a FluentString
type and using it across the resolver in lieu of unicode string, to further narrow down the list of return values. I don't know what the performance impact of this would be, however.
I don't want to block this PR on this discussion but I did want to start it so that you can perhaps consider if it's a follow-up material. For this PR, the current approach works great and it has all the tests to prove it. We should land it without major refactors. Thank you, @spookylukey, this is outstanding work!
README.md
Outdated
To generate translations using the ``fluent.runtime`` package, you start with | ||
the `FluentBundle` class: | ||
|
||
>>> from fluent.bundle import FluentBundle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I didn't understand that, then. I see what you mean: fluent.runtime
the package can provide more than one module namespaced under fluent
.
I think I'd be a bit surprised if pip install fluent.runtime
didn't make import fluent.runtime
work. It might be the choice of the period as the separator in the package name—personally, I would find it easier to understand if that name mapped exactly to the name of the module which is installed.
I don't want to block on this; I'll leave this up to you.
README.md
Outdated
You can specify your own formatting options on the arguments passed in by | ||
wrapping your numeric arguments with `fluent.types.fluent_number`: | ||
|
||
>>> from fluent.bundle.types import fluent_number |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't we also need to move fluent.runtime/fluent/bundle/types.py
to fluent.runtime/fluent/types/__init__.py
to make it a new module namespaced under fluent
?
qux = { 1.0 -> | ||
*[0] A | ||
[1] B | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(No action needed.) This highlights the lack of the resolver spec in Fluent. I'm not sure if 1.0
should be equal to 1
. You might remember the discussion from projectfluent/fluent#155 (comment) about this. I hope to be able to resume this conversation in the coming months and precisely define the expected behavior of comparing Fluent's numbers. In the meantime, thanks for re-implementing the same behavior as manifested by fluent.js
.
Hi @stasm - thanks so much for the review, all your encouragements and helpful comments. For most of your comments I'll just address them directly and update the PR, only if I disagree or have something to add will I comment. So interpret silence as 'thank you I agree'! |
To explain how I got where I did, to the best of my memory - I started off trying to follow Translating that to Python turned out tricky (because So, I ended up going my own way and just following it through, and this is the way it turned out!
I think I was mainly thinking of custom functions which can be written assuming they are dealing with normal float/int/Decimal objects etc.,. This does have the argument of elegance for the user implementing those functions I think. I agree that it does seem strange that the |
@spookylukey, I think this is ready for landing, unless you need a bit more time. Let me know if you'd like me to merge it, or feel free to merge yourself—I've sent you an invite to collaborate on this repo :) |
This the #67 branch merged onto #80
(separate PR to avoid confusing the review that is ongoing on #67).